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 5f18eb7fd3f..cbec7ad4979 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java @@ -26,6 +26,13 @@ public class VmGlobalConfig { public static GlobalConfig VM_EXPUNGE_INTERVAL = new GlobalConfig(CATEGORY, "expungeInterval"); @GlobalConfigValidation(validValues = {"true", "false"}) public static GlobalConfig VM_CLEAN_TRAFFIC = new GlobalConfig(CATEGORY, "cleanTraffic"); + @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 = {"cirrus","vga", "qxl", "virtio"}) @BindResourceConfig(value = {VmInstanceVO.class, ClusterVO.class}) public static GlobalConfig VM_VIDEO_TYPE = new GlobalConfig(CATEGORY, "videoType"); 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 321c7b3325d..028147df8a8 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java @@ -1261,6 +1261,28 @@ public void rollback(FlowRollback trigger, Map data) { } }); + flow(new Flow() { + List done = new ArrayList<>(); + String __name__ = String.format("after-persist-create-vm-extension-%s", finalVo.getUuid()); + + @Override + public void run(FlowTrigger trigger, Map data) { + for (VmInstanceCreateExtensionPoint extension : pluginRgty.getExtensionList(VmInstanceCreateExtensionPoint.class)) { + done.add(extension); + extension.afterPersistVmInstanceVO(finalVo, msg); + } + trigger.next(); + } + + @Override + public void rollback(FlowRollback trigger, Map data) { + Collections.reverse(done); + CollectionUtils.safeForEach(done, + extension -> extension.afterRollbackPersistVmInstanceVO(finalVo, msg)); + trigger.rollback(); + } + }); + flow(new Flow() { List errorCodes = Collections.emptyList(); String __name__ = String.format("instantiate-ssh-key-pair-for-vm-%s", finalVo.getUuid()); 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 f0bdeded2af..4a27fe20ca6 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java @@ -63,6 +63,7 @@ public static CreateVmInstanceMsg fromAPICreateVmInstanceMsg(APICreateVmInstance cmsg.setPrimaryStorageUuidForRootVolume(msg.getPrimaryStorageUuidForRootVolume()); cmsg.setDataVolumeSystemTagsOnIndex(msg.getDataVolumeSystemTagsOnIndex()); cmsg.setStrategy(msg.getStrategy()); + cmsg.setDevicesSpec(msg.getDevicesSpec()); cmsg.setDiskAOs(msg.getDiskAOs()); 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..734ffcfaa4a --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java @@ -0,0 +1,37 @@ +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; +import static org.zstack.header.tpm.TpmErrors.GENERAL_ERROR; + +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(GENERAL_ERROR.toString(), + "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(GENERAL_ERROR.toString(), + "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..e221c03bb4f --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java @@ -0,0 +1,83 @@ +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 int applyKeyProviderWithKek(String tpmUuid, String providerUuid) { + return 0; + } + + @Override + public boolean checkTpmKeyProviderAttached(String tpmUuid) { + return false; + } + + @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(); + } + + @Override + public void backupEncryptedResourceKey(BackupEncryptedResourceKeyContext context) { + // do nothing + logger.debug("ignore backup encrypted resource key request for src resource: " + + context.srcResourceUuid + " -> dest resource: " + context.dstResourceUuid); + } + + @Override + public void restoreEncryptedResourceKey(RestoreEncryptedResourceKeyContext context) { + // do nothing + logger.debug("ignore restore encrypted resource key request for src resource: " + + context.srcResourceUuid + " -> dest resource: " + context.dstResourceUuid); + } + + @Override + public void cleanEncryptedResourceKey(String vmHostBackupFileUuid) { + // do nothing + logger.debug("ignore cleanup encrypted resource key request for VmHostBackupFileVO: " + vmHostBackupFileUuid); + } +} 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..8eef6ad3221 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmApiInterceptor.java @@ -0,0 +1,114 @@ +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.toString(), 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.toString(), 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.toString(), 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(GENERAL_ERROR.toString(), SysErrors.OPERATION_ERROR, + "only allowed for kvm type VM instance")); + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/TpmCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/devices/TpmCascadeExtension.java new file mode 100644 index 00000000000..87e3404d49a --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmCascadeExtension.java @@ -0,0 +1,143 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cascade.AbstractAsyncCascadeExtension; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.CascadeConstant; +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.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.message.MessageReply; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.tpm.message.TpmDeletionMsg; +import org.zstack.header.vm.VmDeletionStruct; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.List; + +import static org.zstack.core.Platform.operr; +import static org.zstack.header.tpm.TpmConstants.SERVICE_ID; +import static org.zstack.header.tpm.TpmErrors.GENERAL_ERROR; +import static org.zstack.utils.CollectionDSL.list; +import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; + +public class TpmCascadeExtension extends AbstractAsyncCascadeExtension { + private static final CLogger logger = Utils.getLogger(TpmCascadeExtension.class); + + @Autowired + private DatabaseFacade dbf; + @Autowired + private CloudBus bus; + + private static final String NAME = TpmVO.class.getSimpleName(); + + @Override + public void asyncCascade(CascadeAction action, Completion completion) { + if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { + handleDeletionCheck(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, CascadeConstant.DELETION_FORCE_DELETE_CODE)) { + handleDeletion(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) { + handleDeletionCleanup(action, completion); + } else { + completion.success(); + } + } + + private List tpmFromAction(CascadeAction action) { + if (VmInstanceVO.class.getSimpleName().equals(action.getParentIssuer())) { + List vmDeletionStructs = action.getParentIssuerContext(); + List vmUuidList = transformAndRemoveNull(vmDeletionStructs, it -> it.getInventory().getUuid()); + + if (vmUuidList.isEmpty()) { + return null; + } + return Q.New(TpmVO.class) + .in(TpmVO_.vmInstanceUuid, vmUuidList) + .list(); + } else if (NAME.equals(action.getParentIssuer())) { + return action.getParentIssuerContext(); + } + + return null; + } + + private void handleDeletionCheck(CascadeAction action, Completion completion) { + completion.success(); + } + + private void handleDeletion(CascadeAction action, Completion completion) { + final List tpmList = tpmFromAction(action); + if (CollectionUtils.isEmpty(tpmList)) { + completion.success(); + return; + } + + new While<>(tpmList).each((tpm, whileCompletion) -> { + TpmDeletionMsg msg = new TpmDeletionMsg(); + msg.setTpmUuid(tpm.getUuid()); + msg.setVmInstanceUuid(tpm.getVmInstanceUuid()); + // delete TPM in cascade must skip VM state checking -> force should always BE TRUE + msg.setForceDelete(true); + bus.makeTargetServiceIdByResourceUuid(msg, SERVICE_ID, msg.getTpmUuid()); + bus.send(msg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.debug(String.format("deleted Tpm[uuid:%s] from VM[uuid:%s]", + tpm.getUuid(), tpm.getVmInstanceUuid())); + whileCompletion.done(); + } else { + whileCompletion.addError(reply.getError()); + whileCompletion.allDone(); + } + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + completion.fail(operr(GENERAL_ERROR.toString(), errorCodeList, + "failed to delete Tpm from VM[uuid:%s]", tpmList.get(0).getVmInstanceUuid())); + return; + } + completion.success(); + } + }); + } + + private void handleDeletionCleanup(CascadeAction action, Completion completion) { + completion.success(); + } + + @Override + public List getEdgeNames() { + return list(VmInstanceVO.class.getSimpleName()); + } + + @Override + public String getCascadeResourceName() { + return NAME; + } + + @Override + public CascadeAction createActionForChildResource(CascadeAction action) { + if (CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + List ctx = tpmFromAction(action); + if (ctx != null) { + return action.copy().setParentIssuer(NAME).setParentIssuerContext(ctx); + } + } + + return null; + } +} 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..b5d1c766249 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java @@ -0,0 +1,100 @@ +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(); + + /** + * Repair providerUuid on existing TPM key ref row where wrapped material already exists. + * @return updated rows + */ + int applyKeyProviderWithKek(String tpmUuid, String providerUuid); + + /** + * Whether an EncryptedResourceKeyRef row exists for this TPM. + */ + boolean checkTpmKeyProviderAttached(String tpmUuid); + + 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); + + static class BackupEncryptedResourceKeyContext { + public String srcResourceUuid; + public String dstResourceUuid; + } + + /** + * Copy the encryption resource key from the source TPM to {@link org.zstack.header.vm.additions.VmHostBackupFileVO} + * or other resources. + */ + void backupEncryptedResourceKey(BackupEncryptedResourceKeyContext context); + + static class RestoreEncryptedResourceKeyContext { + public String srcResourceUuid; + public String dstResourceUuid; + } + + void restoreEncryptedResourceKey(RestoreEncryptedResourceKeyContext context); + + void cleanEncryptedResourceKey(String vmHostBackupFileUuid); +} 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..995d1dd1762 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmMessageAutoCompleter.java @@ -0,0 +1,79 @@ +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) { + validate((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 validate(TpmMessage msg) throws ApiMessageInterceptionException { + String tpmUuid = msg.getTpmUuid(); + String vmUuid = msg.getVmInstanceUuid(); + + if (tpmUuid == null && vmUuid == null) { + throw new ApiMessageInterceptionException(argerr(TPM_NOT_FOUND.toString(), + "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(TPM_NOT_FOUND.toString(), + "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.toString(), 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/VmHostBackupFileCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/devices/VmHostBackupFileCascadeExtension.java new file mode 100644 index 00000000000..386254ae6ef --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmHostBackupFileCascadeExtension.java @@ -0,0 +1,194 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cascade.AbstractAsyncCascadeExtension; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.CascadeConstant; +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.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.message.MessageReply; +import org.zstack.header.vm.VmDeletionStruct; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.VmHostBackupFileDeletionMsg; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.zstack.core.Platform.operr; +import static org.zstack.header.tpm.TpmErrors.GENERAL_ERROR; +import static org.zstack.utils.CollectionDSL.list; +import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; + +public class VmHostBackupFileCascadeExtension extends AbstractAsyncCascadeExtension { + private static final CLogger logger = Utils.getLogger(VmHostBackupFileCascadeExtension.class); + + @Autowired + private DatabaseFacade dbf; + @Autowired + private CloudBus bus; + + private static final String NAME = VmHostBackupFileVO.class.getSimpleName(); + + @Override + public void asyncCascade(CascadeAction action, Completion completion) { + if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { + handleDeletionCheck(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, CascadeConstant.DELETION_FORCE_DELETE_CODE)) { + handleDeletion(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) { + handleDeletionCleanup(action, completion); + } else { + completion.success(); + } + } + + private boolean hasCreatedVmInDeletionContext(CascadeAction action) { + Object raw = action.getParentIssuerContext(); + if (!(raw instanceof List)) { + return false; + } + for (Object o : (List) raw) { + if (!(o instanceof VmDeletionStruct)) { + continue; + } + VmInstanceInventory inv = ((VmDeletionStruct) o).getInventory(); + if (inv != null && VmInstanceState.Created.toString().equals(inv.getState())) { + return true; + } + } + return false; + } + + private List voFromAction(CascadeAction action) { + if (VmInstanceVO.class.getSimpleName().equals(action.getParentIssuer())) { + List vmDeletionStructs = action.getParentIssuerContext(); + List vmUuidList = transformAndRemoveNull(vmDeletionStructs, it -> it.getInventory().getUuid()); + + if (vmUuidList.isEmpty()) { + return null; + } + return Q.New(VmHostBackupFileVO.class) + .in(VmHostBackupFileVO_.resourceUuid, vmUuidList) + .list(); + } + // Note: VolumeSnapshotGroupVO has no cascade extension! + // skip: else if (VolumeSnapshotGroupVO.class.getSimpleName().equals(action.getParentIssuer())) + else if (NAME.equals(action.getParentIssuer())) { + return action.getParentIssuerContext(); + } + + return null; + } + + private void handleDeletionCheck(CascadeAction action, Completion completion) { + completion.success(); + } + + private void handleDeletion(CascadeAction action, Completion completion) { + final List voList = voFromAction(action); + if (CollectionUtils.isEmpty(voList)) { + completion.success(); + return; + } + + Set createdVmUuidsAsResource = findVmUuidsInCreatedState( + voList.stream().map(VmHostBackupFileVO::getResourceUuid).collect(Collectors.toSet())); + if (!createdVmUuidsAsResource.isEmpty()) { + logger.info(String.format( + "VmHostBackupFileCascadeExtension: deleting VmHostBackupFile row(s) tied to Created VM(s) %s with forceDelete=true " + + "(avoids leftovers when VM row is hard-deleted)", + String.join(", ", createdVmUuidsAsResource))); + } + + new While<>(voList).each((vo, whileCompletion) -> { + VmHostBackupFileDeletionMsg msg = new VmHostBackupFileDeletionMsg(); + msg.setUuid(vo.getUuid()); + boolean force = action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE) + || createdVmUuidsAsResource.contains(vo.getResourceUuid()); + msg.setForceDelete(force); + bus.makeLocalServiceId(msg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(msg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.debug(String.format("deleted VmHostBackupFile[uuid:%s] from resource[uuid:%s]", + vo.getUuid(), vo.getResourceUuid())); + whileCompletion.done(); + } else { + whileCompletion.addError(reply.getError()); + whileCompletion.allDone(); + } + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + completion.fail(operr(GENERAL_ERROR.toString(), errorCodeList, + "failed to delete VmHostBackupFile from resource[uuid:%s]", voList.get(0).getResourceUuid())); + return; + } + completion.success(); + } + }); + } + + private Set findVmUuidsInCreatedState(Set candidateVmUuids) { + if (candidateVmUuids.isEmpty()) { + return new HashSet<>(); + } + return new HashSet<>(Q.New(VmInstanceVO.class) + .in(VmInstanceVO_.uuid, candidateVmUuids) + .eq(VmInstanceVO_.state, VmInstanceState.Created) + .select(VmInstanceVO_.uuid) + .listValues()); + } + + private void handleDeletionCleanup(CascadeAction action, Completion completion) { + completion.success(); + } + + @Override + public List getEdgeNames() { + return list( + VmInstanceVO.class.getSimpleName() + + // Note: VolumeSnapshotGroupVO has no cascade extension! + // skip: VolumeSnapshotGroupVO.class.getName() + ); + } + + @Override + public String getCascadeResourceName() { + return NAME; + } + + @Override + public CascadeAction createActionForChildResource(CascadeAction action) { + if (CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + List ctx = voFromAction(action); + if (ctx != null) { + return action.copy().setParentIssuer(NAME).setParentIssuerContext(ctx); + } + } + + return null; + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/VmHostFileCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/devices/VmHostFileCascadeExtension.java new file mode 100644 index 00000000000..67407c5c589 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmHostFileCascadeExtension.java @@ -0,0 +1,189 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cascade.AbstractAsyncCascadeExtension; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.CascadeConstant; +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.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.message.MessageReply; +import org.zstack.header.vm.VmDeletionStruct; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.VmHostFileDeletionMsg; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.zstack.core.Platform.operr; +import static org.zstack.header.tpm.TpmErrors.GENERAL_ERROR; +import static org.zstack.utils.CollectionDSL.list; +import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; + +public class VmHostFileCascadeExtension extends AbstractAsyncCascadeExtension { + private static final CLogger logger = Utils.getLogger(VmHostFileCascadeExtension.class); + + @Autowired + private DatabaseFacade dbf; + @Autowired + private CloudBus bus; + + private static final String NAME = VmHostFileVO.class.getSimpleName(); + + @Override + public void asyncCascade(CascadeAction action, Completion completion) { + if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { + handleDeletionCheck(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, CascadeConstant.DELETION_FORCE_DELETE_CODE)) { + handleDeletion(action, completion); + } else if (action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) { + handleDeletionCleanup(action, completion); + } else { + completion.success(); + } + } + + private boolean hasCreatedVmInDeletionContext(CascadeAction action) { + Object raw = action.getParentIssuerContext(); + if (!(raw instanceof List)) { + return false; + } + for (Object o : (List) raw) { + if (!(o instanceof VmDeletionStruct)) { + continue; + } + VmInstanceInventory inv = ((VmDeletionStruct) o).getInventory(); + if (inv != null && VmInstanceState.Created.toString().equals(inv.getState())) { + return true; + } + } + return false; + } + + private List voFromAction(CascadeAction action) { + if (VmInstanceVO.class.getSimpleName().equals(action.getParentIssuer())) { + List vmDeletionStructs = action.getParentIssuerContext(); + List vmUuidList = transformAndRemoveNull(vmDeletionStructs, it -> it.getInventory().getUuid()); + + if (vmUuidList.isEmpty()) { + return null; + } + return Q.New(VmHostFileVO.class) + .in(VmHostFileVO_.vmInstanceUuid, vmUuidList) + .list(); + } else if (NAME.equals(action.getParentIssuer())) { + return action.getParentIssuerContext(); + } + + return null; + } + + private void handleDeletionCheck(CascadeAction action, Completion completion) { + completion.success(); + } + + private void handleDeletion(CascadeAction action, Completion completion) { + final List voList = voFromAction(action); + if (CollectionUtils.isEmpty(voList)) { + completion.success(); + return; + } + + Set createdVmUuids = findVmUuidsInCreatedState( + voList.stream().map(VmHostFileVO::getVmInstanceUuid).collect(Collectors.toSet())); + if (!createdVmUuids.isEmpty()) { + logger.info(String.format( + "VmHostFileCascadeExtension: deleting VmHostFile row(s) for Created VM(s) %s with forceDelete=true " + + "(avoids leftovers when VM row is hard-deleted)", + String.join(", ", createdVmUuids))); + } + + new While<>(voList).each((vo, whileCompletion) -> { + VmHostFileDeletionMsg msg = new VmHostFileDeletionMsg(); + msg.setUuid(vo.getUuid()); + boolean force = action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE) + || createdVmUuids.contains(vo.getVmInstanceUuid()); + msg.setForceDelete(force); + bus.makeLocalServiceId(msg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(msg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.debug(String.format("deleted VmHostFile[uuid:%s] from VM[uuid:%s]", + vo.getUuid(), vo.getVmInstanceUuid())); + whileCompletion.done(); + } else { + whileCompletion.addError(reply.getError()); + whileCompletion.allDone(); + } + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + completion.fail(operr(GENERAL_ERROR.toString(), errorCodeList, + "failed to delete VmHostFile from VM[uuid:%s]", voList.get(0).getVmInstanceUuid())); + return; + } + completion.success(); + } + }); + } + + private Set findVmUuidsInCreatedState(Set vmUuids) { + if (vmUuids.isEmpty()) { + return new HashSet<>(); + } + return new HashSet<>(Q.New(VmInstanceVO.class) + .in(VmInstanceVO_.uuid, vmUuids) + .eq(VmInstanceVO_.state, VmInstanceState.Created) + .select(VmInstanceVO_.uuid) + .listValues()); + } + + private void handleDeletionCleanup(CascadeAction action, Completion completion) { + completion.success(); + } + + @Override + public List getEdgeNames() { + // Note: host delete -> VmHostFileVO should not be delete: + // TPM/NvRam is too important and should not be deleted so easily. + // And hostUuid will not be updated -> host is not reachable. it is by design. + return list(VmInstanceVO.class.getSimpleName()); + } + + @Override + public String getCascadeResourceName() { + return NAME; + } + + @Override + public CascadeAction createActionForChildResource(CascadeAction action) { + if (CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { + List ctx = voFromAction(action); + if (ctx != null) { + return action.copy().setParentIssuer(NAME).setParentIssuerContext(ctx); + } + } + + return null; + } +} 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..c0592589b9c --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java @@ -0,0 +1,94 @@ +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.identity.Account; +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.setAccountUuid(Account.getAccountUuidOfResource(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/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index e69de29bb2d..7566edba63e 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -0,0 +1,141 @@ +-- vTPM / KMS / NKP schema upgrade for cloud 5.5.28 +-- Migrated from zsv V5.0.0__schema.sql. post-5.0.0 snapshot rollback key backup is not part of this default upgrade. + +-- 1. KeyProviderVO (referenced by EncryptedResourceKeyRefVO) +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; + +-- 2. KmsVO +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; + +-- 3. KmsIdentityVO (references KmsVO) +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; + +-- 4. NkpVO +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; + +-- 5. EncryptedResourceKeyRefVO (references KeyProviderVO) +-- This table does not exist in cloud 5.5.28, so do not run orphan cleanup before creation. +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; + +-- 6. TpmVO (references VmInstanceEO) +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; + +-- 7. VmHostFileVO +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; + +-- 8. VmHostBackupFileVO +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; + +-- 9. VmHostFileContentVO (references ResourceVO) +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; + +-- 10. HostKeyIdentityVO (references HostEO) +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; diff --git a/conf/errorCodes/tpm.xml b/conf/errorCodes/tpm.xml new file mode 100644 index 00000000000..37e67481b1e --- /dev/null +++ b/conf/errorCodes/tpm.xml @@ -0,0 +1,23 @@ + + TPM + + + 1000 + TPM related general error + + + + 1701 + TPM already exists for the VM + + + + 1702 + TPM not found + + + + 1703 + VM is not in a valid state for TPM operation + + diff --git a/conf/persistence.xml b/conf/persistence.xml index b66d6319ff7..0e4fe14b6e8 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -24,6 +24,7 @@ org.zstack.header.cluster.ClusterEO org.zstack.header.host.HostVO org.zstack.header.host.HostEO + org.zstack.header.host.HostKeyIdentityVO org.zstack.header.host.HostNetworkLabelVO org.zstack.header.host.CpuFeaturesHistoryVO org.zstack.header.storage.primary.PrimaryStorageVO @@ -181,6 +182,10 @@ org.zstack.header.identity.role.SystemRoleVO org.zstack.header.identity.role.RolePolicyStatementVO org.zstack.header.core.captcha.CaptchaVO + 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.vm.cdrom.VmCdRomVO org.zstack.header.core.trash.InstallPathRecycleVO org.zstack.header.vm.VmPriorityConfigVO 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/springConfigXml/Kvm.xml b/conf/springConfigXml/Kvm.xml index cdb2f667c15..0ed63400999 100755 --- a/conf/springConfigXml/Kvm.xml +++ b/conf/springConfigXml/Kvm.xml @@ -212,12 +212,12 @@ - - - - - - + + + + + + @@ -256,7 +256,46 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/springConfigXml/VmInstanceManager.xml b/conf/springConfigXml/VmInstanceManager.xml index 09a9cb8d0e5..5dcc4d6ea2f 100755 --- a/conf/springConfigXml/VmInstanceManager.xml +++ b/conf/springConfigXml/VmInstanceManager.xml @@ -1,276 +1,306 @@ - - - - - - - - - - - - - - - - - - - org.zstack.compute.vm.VmImageSelectBackupStorageFlow - org.zstack.compute.vm.VmAllocateHostAndPrimaryStorageFlow - org.zstack.compute.vm.VmAllocateVolumeFlow - org.zstack.compute.vm.VmAllocateNicFlow - org.zstack.compute.vm.VmAllocateNicIpFlow - org.zstack.compute.vm.VmAllocateSdnNicFlow - org.zstack.compute.vm.VmAllocateCdRomFlow - org.zstack.compute.vm.VmInstantiateResourcePreFlow - org.zstack.compute.vm.VmCreateOnHypervisorFlow - org.zstack.compute.vm.VmInstantiateResourcePostFlow - - - - - org.zstack.compute.vm.BeforeVmStopOnHypervisorFlow - org.zstack.compute.vm.VmStopOnHypervisorFlow - org.zstack.compute.vm.VmReturnHostFlow - org.zstack.compute.vm.VmReleaseResourceFlow - - - - - org.zstack.compute.vm.BeforeVmStopOnHypervisorFlow - org.zstack.compute.vm.VmStopOnHypervisorFlow - org.zstack.compute.vm.VmReleaseResourceFlow - org.zstack.compute.vm.VmImageSelectBackupStorageFlow - org.zstack.compute.vm.VmAllocateNicForStartingVmFlow - org.zstack.compute.vm.VmInstantiateResourcePreFlow - org.zstack.compute.vm.VmStartOnHypervisorFlow - org.zstack.compute.vm.VmInstantiateResourcePostFlow - - - - - org.zstack.compute.vm.VmAllocateHostForStoppedVmFlow - org.zstack.compute.vm.VmImageSelectBackupStorageFlow - org.zstack.compute.vm.VmAllocateNicForStartingVmFlow - org.zstack.compute.vm.VmAllocateSdnNicFlow - org.zstack.compute.vm.VmInstantiateResourcePreFlow - org.zstack.compute.vm.VmStartOnHypervisorFlow - org.zstack.compute.vm.VmInstantiateResourcePostFlow - - - - - org.zstack.compute.vm.VmMigrationCheckL2NetworkOnHostFlow - org.zstack.compute.vm.VmAllocateHostForMigrateVmFlow - org.zstack.compute.vm.VmMigrateCallExtensionFlow - org.zstack.compute.vm.VmMigrateOnHypervisorFlow - org.zstack.compute.vm.VmReturnHostFlow - - - - - org.zstack.compute.vm.VmDestroyOnHypervisorFlow - org.zstack.compute.vm.VmReturnHostFlow - org.zstack.compute.vm.VmReleaseResourceFlow - org.zstack.compute.vm.VmReturnReleaseNicFlow - org.zstack.compute.vm.VmPostReleaseNicFlow - org.zstack.compute.vm.VmDeleteVolumeFlow - - - - - org.zstack.compute.vm.VmAllocatePrimaryStorageForAttachingDiskFlow - org.zstack.compute.vm.VmInstantiateAttachingVolumeFlow - org.zstack.compute.vm.VmAfterInstantiateVolumeInAttachingVolumeFlow - org.zstack.compute.vm.VmAssignDeviceIdToAttachingVolumeFlow - org.zstack.compute.vm.VmAttachVolumeOnHypervisorFlow - - - - - org.zstack.compute.vm.VmDownloadIsoFlow - org.zstack.compute.vm.AttachIsoOnHypervisorFlow - - - - - org.zstack.compute.vm.DetachIsoOnHypervisorFlow - org.zstack.compute.vm.DetachIsoOnPrimaryStorageFlow - - - - + + + + + + + + + + + + + + + + + + + org.zstack.compute.vm.VmImageSelectBackupStorageFlow + org.zstack.compute.vm.VmAllocateHostAndPrimaryStorageFlow + org.zstack.compute.vm.VmAllocateVolumeFlow + org.zstack.compute.vm.VmAllocateNicFlow + org.zstack.compute.vm.VmAllocateNicIpFlow + org.zstack.compute.vm.VmAllocateSdnNicFlow + org.zstack.compute.vm.VmAllocateCdRomFlow + org.zstack.compute.vm.VmInstantiateResourcePreFlow + org.zstack.compute.vm.VmCreateOnHypervisorFlow + org.zstack.compute.vm.VmInstantiateResourcePostFlow + + + + + org.zstack.compute.vm.BeforeVmStopOnHypervisorFlow + org.zstack.compute.vm.VmStopOnHypervisorFlow + org.zstack.compute.vm.VmReturnHostFlow + org.zstack.compute.vm.VmReleaseResourceFlow + + + + + org.zstack.compute.vm.BeforeVmStopOnHypervisorFlow + org.zstack.compute.vm.VmStopOnHypervisorFlow + org.zstack.compute.vm.VmReleaseResourceFlow + org.zstack.compute.vm.VmImageSelectBackupStorageFlow + org.zstack.compute.vm.VmAllocateNicForStartingVmFlow + org.zstack.compute.vm.VmInstantiateResourcePreFlow + org.zstack.compute.vm.VmStartOnHypervisorFlow + org.zstack.compute.vm.VmInstantiateResourcePostFlow + + + + + org.zstack.compute.vm.VmAllocateHostForStoppedVmFlow + org.zstack.compute.vm.VmImageSelectBackupStorageFlow + org.zstack.compute.vm.VmAllocateNicForStartingVmFlow + org.zstack.compute.vm.VmAllocateSdnNicFlow + org.zstack.compute.vm.VmInstantiateResourcePreFlow + org.zstack.compute.vm.VmStartOnHypervisorFlow + org.zstack.compute.vm.VmInstantiateResourcePostFlow + + + + + org.zstack.compute.vm.VmMigrationCheckL2NetworkOnHostFlow + org.zstack.compute.vm.VmAllocateHostForMigrateVmFlow + org.zstack.compute.vm.VmMigrateCallExtensionFlow + org.zstack.compute.vm.VmMigrateOnHypervisorFlow + org.zstack.compute.vm.VmReturnHostFlow + + + + + org.zstack.compute.vm.VmDestroyOnHypervisorFlow + org.zstack.compute.vm.VmReturnHostFlow + org.zstack.compute.vm.VmReleaseResourceFlow + org.zstack.compute.vm.VmReturnReleaseNicFlow + org.zstack.compute.vm.VmPostReleaseNicFlow + org.zstack.compute.vm.VmDeleteVolumeFlow + + + + + org.zstack.compute.vm.VmAllocatePrimaryStorageForAttachingDiskFlow + org.zstack.compute.vm.VmInstantiateAttachingVolumeFlow + org.zstack.compute.vm.VmAfterInstantiateVolumeInAttachingVolumeFlow + org.zstack.compute.vm.VmAssignDeviceIdToAttachingVolumeFlow + org.zstack.compute.vm.VmAttachVolumeOnHypervisorFlow + + + + + org.zstack.compute.vm.VmDownloadIsoFlow + org.zstack.compute.vm.AttachIsoOnHypervisorFlow + + + + + org.zstack.compute.vm.DetachIsoOnHypervisorFlow + org.zstack.compute.vm.DetachIsoOnPrimaryStorageFlow + + + + org.zstack.compute.vm.VmExpungeSdnNicFlow - org.zstack.compute.vm.VmExpungeRootVolumeFlow - org.zstack.compute.vm.VmExpungeMemoryVolumeFlow - org.zstack.compute.vm.VmExpungeCacheVolumeFlow - - - - - org.zstack.compute.vm.PauseVmOnHypervisorFlow - - - - - org.zstack.compute.vm.ResumeVmOnHypervisorFlow - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - virtio - e1000 - rtl8139 - pcnet - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + org.zstack.compute.vm.VmExpungeRootVolumeFlow + org.zstack.compute.vm.VmExpungeMemoryVolumeFlow + org.zstack.compute.vm.VmExpungeCacheVolumeFlow + + + + + org.zstack.compute.vm.PauseVmOnHypervisorFlow + + + + + org.zstack.compute.vm.ResumeVmOnHypervisorFlow + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + virtio + e1000 + rtl8139 + pcnet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/org/zstack/core/Platform.java b/core/src/main/java/org/zstack/core/Platform.java index 78b184d12e7..1e2dffecde6 100755 --- a/core/src/main/java/org/zstack/core/Platform.java +++ b/core/src/main/java/org/zstack/core/Platform.java @@ -966,6 +966,14 @@ public static ErrorCode err(String globalErrorCode, Enum errCode, String fmt, Ob return err(globalErrorCode, errCode, null, fmt, args); } + public static ErrorCode err(Enum errCode, String fmt, Object...args) { + return err(errCode.toString(), errCode, fmt, args); + } + + public static ErrorCode err(Enum errCode, ErrorCode cause, String fmt, Object...args) { + return err(errCode.toString(), errCode, cause, fmt, args); + } + public static ErrorCode err(String globalErrorCode, Enum errCode, ErrorCode cause, String fmt, Object...args) { ErrorFacade errf = getComponentLoader().getComponent(ErrorFacade.class); String details = null; @@ -1152,6 +1160,10 @@ public static ErrorCode operr(String globalErrorCode, String fmt, Object...args) return err(globalErrorCode, SysErrors.OPERATION_ERROR, fmt, args); } + public static ErrorCode operr(String fmt, Object...args) { + return err(SysErrors.OPERATION_ERROR.toString(), SysErrors.OPERATION_ERROR, fmt, args); + } + public static ErrorCode operr(String globalErrorCode, ErrorCode cause, String fmt, Object...args) { return err(globalErrorCode, SysErrors.OPERATION_ERROR, cause, fmt, args); } @@ -1164,6 +1176,10 @@ public static ErrorCode argerr(String globalErrorCode, String fmt, Object...args return err(globalErrorCode, SysErrors.INVALID_ARGUMENT_ERROR, fmt, args); } + public static ErrorCode argerr(String fmt, Object...args) { + return err(SysErrors.INVALID_ARGUMENT_ERROR.toString(), SysErrors.INVALID_ARGUMENT_ERROR, fmt, args); + } + public static ErrorCode touterr(String globalErrorCode, String fmt, Object...args) { return err(globalErrorCode, SysErrors.TIMEOUT, fmt, args); } 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 a78a2172e97..d2a86952063 100755 --- a/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java +++ b/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java @@ -15,6 +15,7 @@ import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.telemetry.TelemetryFacade; import org.zstack.core.telemetry.TelemetryGlobalProperty; +import org.zstack.header.core.AsyncBackup; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; @@ -31,6 +32,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import static org.zstack.core.Platform.inerr; import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; @@ -71,6 +73,7 @@ public class SimpleFlowChain implements FlowTrigger, FlowRollback, FlowChain, Fl private List> afterDone = new ArrayList<>(); private List> afterError = new ArrayList<>(); private List> afterFinal = new ArrayList<>(); + private List asyncBackups = new ArrayList<>(); private boolean isFailCalled; @@ -291,11 +294,20 @@ public SimpleFlowChain() { id = "FCID_" + Platform.getUuid().substring(0, 8); } + public SimpleFlowChain(String name) { + this(); + this.name = name; + } + public SimpleFlowChain(Map data) { id = "FCID_" + Platform.getUuid().substring(0, 8); this.data.putAll(data); } + public static SimpleFlowChain of(String chainName) { + return new SimpleFlowChain(chainName); + } + @Override public List getFlows() { return flows; @@ -379,6 +391,10 @@ public SimpleFlowChain then(Flow flow) { return this; } + public SimpleFlowChain then(String flowName, Consumer consumer) { + return then(Flow.of(flowName).handle(consumer).build()); + } + public SimpleFlowChain ctxHandler(FlowContextHandler handler) { DebugUtils.Assert(contextHandler == null, "there has been an FlowContextHandler installed"); contextHandler = handler; @@ -391,6 +407,18 @@ public SimpleFlowChain error(FlowErrorHandler handler) { return this; } + public SimpleFlowChain error(Consumer handler) { + DebugUtils.Assert(!asyncBackups.isEmpty(), "propagateExceptionTo() must be called before error(Consumer)"); + AsyncBackup first = asyncBackups.get(0); + AsyncBackup[] others = asyncBackups.subList(1, asyncBackups.size()).toArray(new AsyncBackup[0]); + return error(new FlowErrorHandler(first, others) { + @Override + public void handle(ErrorCode errCode, Map data) { + handler.accept(errCode); + } + }); + } + @Override public FlowChain Finally(FlowFinallyHandler handler) { finallyHandler = handler; @@ -440,6 +468,28 @@ public SimpleFlowChain done(FlowDoneHandler handler) { return this; } + public SimpleFlowChain propagateExceptionTo(AsyncBackup... backups) { + DebugUtils.Assert(backups != null, "backups must not be null"); + asyncBackups.addAll(Arrays.asList(backups)); + asyncBackups.removeIf(Objects::isNull); + if (asyncBackups.isEmpty()) { + asyncBackups.add(null); + } + return this; + } + + public SimpleFlowChain done(Runnable runnable) { + DebugUtils.Assert(!asyncBackups.isEmpty(), "propagateExceptionTo() must be called before done(Runnable)"); + AsyncBackup first = asyncBackups.get(0); + AsyncBackup[] others = asyncBackups.subList(1, asyncBackups.size()).toArray(new AsyncBackup[0]); + return done(new FlowDoneHandler(first, others) { + @Override + public void handle(Map data) { + runnable.run(); + } + }); + } + private void collectAfterRunnable(Flow flow) { List ad = FieldUtils.getAnnotatedFieldsOnThisClass(AfterDone.class, flow.getClass()); for (Field f : ad) { diff --git a/header/src/main/java/org/zstack/header/core/workflow/Flow.java b/header/src/main/java/org/zstack/header/core/workflow/Flow.java index 7fa262ded82..33f8d2ad8f4 100755 --- a/header/src/main/java/org/zstack/header/core/workflow/Flow.java +++ b/header/src/main/java/org/zstack/header/core/workflow/Flow.java @@ -1,6 +1,12 @@ package org.zstack.header.core.workflow; +import org.zstack.utils.DebugUtils; +import org.zstack.utils.FieldUtils; + import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; public interface Flow { void run(FlowTrigger trigger, Map data); @@ -10,4 +16,103 @@ public interface Flow { default boolean skip(Map data) { return false; } + + default String name() { + String innerName = FieldUtils.getFieldValue("__name__", this); + if (innerName != null && !innerName.trim().isEmpty()) { + return innerName; + } + return String.format("%s", this.getClass().getSimpleName()); + } + + @SuppressWarnings("rawtypes") + class FlowBuilder { + private final String flowName; + private Predicate skipPredicate; + private BiConsumer triggerConsumer; + private BiConsumer rollbackConsumer; + + private FlowBuilder(String flowName) { + DebugUtils.Assert(flowName != null, "flowName should not be null"); + this.flowName = flowName; + } + + public FlowBuilder skipIf(Predicate predicate) { + DebugUtils.Assert(predicate != null, "skipPredicate should not be null"); + this.skipPredicate = predicate; + return this; + } + + public FlowBuilder runIf(Predicate predicate) { + DebugUtils.Assert(predicate != null, "runIf predicate should not be null"); + this.skipPredicate = predicate.negate(); + return this; + } + + public FlowBuilder handle(BiConsumer consumer) { + DebugUtils.Assert(consumer != null, "handle consumer should not be null"); + this.triggerConsumer = consumer; + return this; + } + + public FlowBuilder handle(Consumer consumer) { + DebugUtils.Assert(consumer != null, "handle consumer should not be null"); + this.triggerConsumer = (trigger, data) -> consumer.accept(trigger); + return this; + } + + public FlowBuilder rollback(BiConsumer consumer) { + DebugUtils.Assert(consumer != null, "rollback consumer should not be null"); + this.rollbackConsumer = consumer; + return this; + } + + public FlowBuilder rollback(Consumer consumer) { + DebugUtils.Assert(consumer != null, "rollback consumer should not be null"); + this.rollbackConsumer = (trigger, data) -> consumer.accept(trigger); + return this; + } + + public Flow build() { + DebugUtils.Assert(triggerConsumer != null, "handle() must be called before build()"); + Predicate finalSkipPredicate = skipPredicate; + BiConsumer finalTriggerConsumer = triggerConsumer; + BiConsumer finalRollbackConsumer = rollbackConsumer; + + return new Flow() { + @Override + public void run(FlowTrigger trigger, Map data) { + finalTriggerConsumer.accept(trigger, data); + } + + @Override + public void rollback(FlowRollback trigger, Map data) { + if (finalRollbackConsumer == null) { + trigger.rollback(); + } else { + finalRollbackConsumer.accept(trigger, data); + } + } + + @Override + public boolean skip(Map data) { + return finalSkipPredicate != null && finalSkipPredicate.test(data); + } + + @Override + public String name() { + return flowName; + } + + @Override + public String toString() { + return name(); + } + }; + } + } + + static FlowBuilder of(String flowName) { + return new FlowBuilder(flowName); + } } diff --git a/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java b/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java index d180b6b4ccb..447a2e68660 100755 --- a/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java +++ b/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java @@ -170,11 +170,32 @@ public ErrorCode causedBy(ErrorCode cause) { return this; } + public ErrorCode withCause(ErrorCode cause) { + return causedBy(cause); + } + + public ErrorCode withCause(ErrorCodeList cause) { + return causedBy(cause); + } + public ErrorCode causedBy(List cause) { ((ErrorCodeList) this).setCauses(cause); return this; } + public ErrorCode withCause(List cause) { + ErrorCodeList errList = new ErrorCodeList(); + errList.setCauses(cause); + return causedBy(errList); + } + + public ErrorCode withException(Object e) { + if (e != null) { + putToOpaque("exception", e.toString()); + } + return this; + } + public String getElaboration() { return elaboration; } diff --git a/header/src/main/java/org/zstack/header/errorcode/ErrorCodeList.java b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeList.java index 8a0082a2f67..8873e188f4b 100755 --- a/header/src/main/java/org/zstack/header/errorcode/ErrorCodeList.java +++ b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeList.java @@ -20,6 +20,21 @@ public void setCauses(List causes) { this.causes = causes; } + public boolean isEmpty() { + return causes == null || causes.isEmpty(); + } + + public boolean hasError() { + return !isEmpty(); + } + + public int size() { + return causes == null ? 0 : causes.size(); + } + + public boolean add(ErrorCode cause) { + return causes.add(cause); + } /** * 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..06238fd1a76 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO.java @@ -0,0 +1,93 @@ +package org.zstack.header.host; + +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PreUpdate; +import javax.persistence.Table; +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/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..b8db7d914a6 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,8 @@ public boolean mask() { enum Type { Simple, Tag, - Uri; + Uri, + LongText; public boolean simple() { return this == Simple; @@ -39,6 +40,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/ResolveVtpmLibvirtSecretOnHypervisorMsg.java b/header/src/main/java/org/zstack/header/secret/ResolveVtpmLibvirtSecretOnHypervisorMsg.java new file mode 100644 index 00000000000..e4ec14cb72f --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/ResolveVtpmLibvirtSecretOnHypervisorMsg.java @@ -0,0 +1,26 @@ +package org.zstack.header.secret; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; + +public class ResolveVtpmLibvirtSecretOnHypervisorMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + private String vmUuid; + + @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; + } +} diff --git a/header/src/main/java/org/zstack/header/secret/ResolveVtpmLibvirtSecretOnHypervisorReply.java b/header/src/main/java/org/zstack/header/secret/ResolveVtpmLibvirtSecretOnHypervisorReply.java new file mode 100644 index 00000000000..af9927592d5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/ResolveVtpmLibvirtSecretOnHypervisorReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.secret; + +import org.zstack.header.message.MessageReply; + +public class ResolveVtpmLibvirtSecretOnHypervisorReply extends MessageReply { + 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/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/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java index 9e52d36b0d3..bb19e991eba 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 @@ -18,6 +18,7 @@ public class CreateVolumesSnapshotOverlayInnerMsg extends NeedReplyMessage imple private List lockedVmInstanceUuids; private List lockedVolumeUuids; + private boolean backupHostFileIfNeeded; public List getLockedVmInstanceUuids() { return lockedVmInstanceUuids; @@ -63,4 +64,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/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/tpm/RBACInfo.java b/header/src/main/java/org/zstack/header/tpm/RBACInfo.java new file mode 100644 index 00000000000..1b93517ead8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/RBACInfo.java @@ -0,0 +1,35 @@ +package org.zstack.header.tpm; + +import org.zstack.header.identity.rbac.RBACDescription; +import org.zstack.header.tpm.api.APIAddTpmMsg; +import org.zstack.header.tpm.api.APIGetTpmCapabilityMsg; +import org.zstack.header.tpm.api.APIQueryTpmMsg; +import org.zstack.header.tpm.api.APIRemoveTpmMsg; +import org.zstack.header.tpm.api.APIUpdateTpmMsg; + +public class RBACInfo implements RBACDescription { + @Override + public void permissions() { + permissionBuilder() + .adminOnlyAPIs( + APIAddTpmMsg.class, + APIGetTpmCapabilityMsg.class, + APIQueryTpmMsg.class, + APIRemoveTpmMsg.class, + APIUpdateTpmMsg.class + ) + .build(); + } + + @Override + public void contributeToRoles() { + } + + @Override + public void roles() { + } + + @Override + public void globalReadableResources() { + } +} 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..ca941b771d9 --- /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.5.28" + clz TpmInventory.class + } + field { + name "success" + desc "添加是否成功" + type "boolean" + since "5.5.28" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIAddTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.5.28" + 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..14e222e01a8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsg.java @@ -0,0 +1,50 @@ +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; + +@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(DocUtils.uuidForAPIDoc()); + msg.setVmInstanceUuid(DocUtils.uuidForAPIDoc()); + 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..0a0e3b7fd05 --- /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.5.28" + } + column { + name "vmInstanceUuid" + enclosedIn "params" + desc "虚拟机 UUID" + location "body" + type "String" + optional false + since "5.5.28" + } + column { + name "resourceUuid" + enclosedIn "params" + desc "资源 UUID" + location "body" + type "String" + optional true + since "5.5.28" + } + column { + name "tagUuids" + enclosedIn "params" + desc "标签 UUID 列表" + location "body" + type "List" + optional true + since "5.5.28" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.5.28" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.5.28" + } + } + } + + 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..e1f641a3ed1 --- /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.uuidForAPIDoc()); + 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..09bc8a03b9e --- /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.5.28" + } + column { + name "vmInstanceUuid" + enclosedIn "" + desc "虚拟机 UUID" + location "query" + type "String" + optional true + since "5.5.28" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "query" + type "List" + optional true + since "5.5.28" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "query" + type "List" + optional true + since "5.5.28" + } + } + } + + 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..28acfd60836 --- /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.5.28" + clz TpmCapabilityView.class + } + field { + name "success" + desc "获取是否成功" + type "boolean" + since "5.5.28" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIGetTpmCapabilityReply.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.5.28" + 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..521c0eed068 --- /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.uuidForAPIDoc()); + } +} 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..dc95a99c824 --- /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.5.28" + clz TpmInventory.class + } + field { + name "success" + desc "查询是否成功" + type "boolean" + since "5.5.28" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIQueryTpmReply.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.5.28" + 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..5666b4b62a8 --- /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.5.28" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIRemoveTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.5.28" + 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..711a4dbd679 --- /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.uuidForAPIDoc()); + 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..375bf213e96 --- /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.5.28" + } + column { + name "vmInstanceUuid" + enclosedIn "" + desc "虚拟机 UUID" + location "query" + type "String" + optional true + since "5.5.28" + } + column { + name "deleteMode" + enclosedIn "" + desc "删除模式(Permissive / Enforcing,Permissive)" + location "query" + type "String" + optional true + since "5.5.28" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "query" + type "List" + optional true + since "5.5.28" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "query" + type "List" + optional true + since "5.5.28" + } + } + } + + 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..b3cdb49d5e7 --- /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.5.28" + clz TpmInventory.class + } + field { + name "success" + desc "更新是否成功" + type "boolean" + since "5.5.28" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIUpdateTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.5.28" + 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..e7a05259337 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsg.java @@ -0,0 +1,61 @@ +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; + +@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(DocUtils.uuidForAPIDoc()); + msg.setVmInstanceUuid(DocUtils.uuidForAPIDoc()); + 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..c30deae2b8d --- /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.5.28" + } + column { + name "tpmUuid" + enclosedIn "updateTpm" + desc "TPM UUID" + location "body" + type "String" + optional true + since "5.5.28" + } + column { + name "keyProviderUuid" + enclosedIn "updateTpm" + desc "密钥提供程序 UUID" + location "body" + type "String" + optional true + since "5.5.28" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.5.28" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.5.28" + } + } + } + + response { + clz APIUpdateTpmEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/PackageInfo.java b/header/src/main/java/org/zstack/header/tpm/api/PackageInfo.java new file mode 100644 index 00000000000..d0450d19f0b --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/PackageInfo.java @@ -0,0 +1,7 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.rest.SDKPackage; + +@SDKPackage(packageName="org.zstack.sdk.tpm") +public class PackageInfo { +} 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/PackageInfo.java b/header/src/main/java/org/zstack/header/tpm/entity/PackageInfo.java new file mode 100644 index 00000000000..57a24e333e9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/PackageInfo.java @@ -0,0 +1,7 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.rest.SDKPackage; + +@SDKPackage(packageName="org.zstack.sdk.tpm") +public class PackageInfo { +} 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..da477e56f86 --- /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.5.28" + } + field { + name "name" + desc "TPM 资源名称" + type "String" + since "5.5.28" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.5.28" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.28" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.28" + } + ref { + name "fileRefs" + path "org.zstack.header.tpm.entity.TpmCapabilityView.fileRefs" + desc "TPM 相关的主机侧文件或目录数据列表" + type "List" + since "5.5.28" + clz VmHostFileInventory.class + } + field { + name "edkVersion" + desc "EDK 套件版本" + type "String" + since "5.5.28" + } + field { + name "swtpmVersion" + desc "SWTPM 版本" + type "String" + since "5.5.28" + } + field { + name "resetTpmAfterVmCloneConfig" + desc "是否在虚拟机克隆后重置 TPM 状态的配置" + type "boolean" + since "5.5.28" + } +} 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..7b35ffec28c --- /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.uuidForAPIDoc()); + tpm.setVmInstanceUuid(DocUtils.uuidForAPIDoc()); + tpm.setName("TPM-for-VM-" + tpm.getVmInstanceUuid()); + tpm.setCreateDate(new Timestamp(DocUtils.date)); + tpm.setLastOpDate(new Timestamp(DocUtils.date)); + 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..65b65d808d5 --- /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.5.28" + } + field { + name "name" + desc "TPM 资源名称" + type "String" + since "5.5.28" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.5.28" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.28" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.28" + } + ref { + name "hostRefs" + path "org.zstack.header.tpm.entity.TpmInventory.hostRefs" + desc "TPM 与主机的相关数据列表" + type "List" + since "5.5.28" + 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..5afeff7b568 --- /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.message.DocUtils; +import org.zstack.header.rest.APINoSee; + +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(DocUtils.uuidForAPIDoc()); + 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..56696ad4455 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/AddTpmMsg.java @@ -0,0 +1,63 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.tpm.api.APIAddTpmMsg; + +public class AddTpmMsg extends NeedReplyMessage { + /** + * Create new key with the provider uuid. + * "keyProviderUuid" and "resourceUuidKeyFrom" should not be set at the same time. + */ + private String keyProviderUuid; + /** + * Copy key from the resource uuid. + * "keyProviderUuid" and "resourceUuidKeyFrom" should not be set at the same time. + */ + private String resourceUuidKeyFrom; + + private String vmInstanceUuid; + /** + * for creating TpmVO + */ + private String tpmUuid; + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getResourceUuidKeyFrom() { + return resourceUuidKeyFrom; + } + + public void setResourceUuidKeyFrom(String resourceUuidKeyFrom) { + this.resourceUuidKeyFrom = resourceUuidKeyFrom; + } + + 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/BackupTpmEncryptionKeyMsg.java b/header/src/main/java/org/zstack/header/tpm/message/BackupTpmEncryptionKeyMsg.java new file mode 100644 index 00000000000..e2b0f8eaa4f --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/BackupTpmEncryptionKeyMsg.java @@ -0,0 +1,24 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; + +public class BackupTpmEncryptionKeyMsg extends NeedReplyMessage { + private String srcResourceUuid; + private String dstResourceUuid; + + public String getSrcResourceUuid() { + return srcResourceUuid; + } + + public void setSrcResourceUuid(String srcResourceUuid) { + this.srcResourceUuid = srcResourceUuid; + } + + public String getDstResourceUuid() { + return dstResourceUuid; + } + + public void setDstResourceUuid(String dstResourceUuid) { + this.dstResourceUuid = dstResourceUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/BackupTpmEncryptionKeyReply.java b/header/src/main/java/org/zstack/header/tpm/message/BackupTpmEncryptionKeyReply.java new file mode 100644 index 00000000000..2ec8bc784a8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/BackupTpmEncryptionKeyReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.MessageReply; + +public class BackupTpmEncryptionKeyReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionMsg.java b/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionMsg.java new file mode 100644 index 00000000000..314a62a1db8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionMsg.java @@ -0,0 +1,34 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.APIDeleteMessage; +import org.zstack.header.tpm.api.APIRemoveTpmMsg; +import org.zstack.header.message.DeletionMessage; + +public class TpmDeletionMsg extends DeletionMessage { + 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 TpmDeletionMsg valueOf(APIRemoveTpmMsg api) { + TpmDeletionMsg msg = new TpmDeletionMsg(); + msg.setTpmUuid(api.getTpmUuid()); + msg.setVmInstanceUuid(api.getVmInstanceUuid()); + msg.setForceDelete(api.getDeletionMode() == APIDeleteMessage.DeletionMode.Enforcing); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionReply.java b/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionReply.java new file mode 100644 index 00000000000..39e05dc7a24 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/TpmDeletionReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.MessageReply; + +public class TpmDeletionReply extends MessageReply { +} 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 6c0155ca41b..763c3468cf2 100644 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java @@ -109,6 +109,8 @@ public class APICreateVmInstanceFromVolumeSnapshotGroupMsg extends APICreateMess @APIParam(required = false) private Map> dataVolumeSystemTags; + @APIParam(required = false) + private Boolean resetTpm; public String getName() { return name; @@ -274,4 +276,12 @@ public Map> getDataVolumeSystemTags() { public void setDataVolumeSystemTags(Map> dataVolumeSystemTags) { this.dataVolumeSystemTags = dataVolumeSystemTags; } + + public Boolean getResetTpm() { + return resetTpm; + } + + public void setResetTpm(Boolean resetTpm) { + this.resetTpm = resetTpm; + } } 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 d6c0a4fd7a7..93c9077b8cc 100755 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java @@ -17,6 +17,7 @@ import org.zstack.header.tag.TagResourceType; import org.zstack.header.volume.VolumeInventory; import org.zstack.header.zone.ZoneVO; +import org.zstack.header.vm.devices.VmDevicesSpec; import java.util.Collections; import java.util.List; @@ -116,6 +117,9 @@ public class APICreateVmInstanceMsg extends APICreateMessage implements APIAudit @APIParam(required = false) private String vmNicParams; + + @APIParam(required = false) + private VmDevicesSpec devicesSpec; /** * @desc see type of :ref:`VmInstanceInventory` * @choices - UserVm @@ -453,6 +457,14 @@ public void setVmNicParams(String vmNicParams) { this.vmNicParams = vmNicParams; } + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } + public List getDataDiskOfferingUuids() { return dataDiskOfferingUuids; } diff --git a/header/src/main/java/org/zstack/header/vm/ArchiveVmBundle.java b/header/src/main/java/org/zstack/header/vm/ArchiveVmBundle.java index 216c57c3387..7f8913fe8cd 100644 --- a/header/src/main/java/org/zstack/header/vm/ArchiveVmBundle.java +++ b/header/src/main/java/org/zstack/header/vm/ArchiveVmBundle.java @@ -5,6 +5,7 @@ */ public class ArchiveVmBundle { VmInstanceInventory vmInventory; + String xml; public ArchiveVmBundle() { } @@ -13,6 +14,10 @@ public ArchiveVmBundle(VmInstanceInventory vmInventory) { this.vmInventory = vmInventory; } + public ArchiveVmBundle(String xml) { + this.xml = xml; + } + public VmInstanceInventory getVmInventory() { return vmInventory; } @@ -21,7 +26,18 @@ public void setVmInventory(VmInstanceInventory vmInventory) { this.vmInventory = vmInventory; } + public String getXml() { + return xml; + } + + public void setXml(String xml) { + this.xml = xml; + } + public UpdateVmInstanceMsg getUpdateVmMessage() { + if (getVmInventory() == null) { + throw new IllegalStateException("vmInventory is null; cannot build UpdateVmInstanceMsg"); + } UpdateVmInstanceMsg umsg = new UpdateVmInstanceMsg(); umsg.setUuid(getVmInventory().getUuid()); umsg.setName(getVmInventory().getName()); 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 dc364097a8f..aa15ccbd644 100755 --- a/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java @@ -1,6 +1,7 @@ package org.zstack.header.vm; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.devices.VmDevicesSpec; import java.util.ArrayList; import java.util.List; @@ -43,6 +44,7 @@ public class CreateVmInstanceMsg extends NeedReplyMessage implements CreateVmIns private Map> dataVolumeSystemTagsOnIndex; private List disableL3Networks; private List sshKeyPairUuids; + private VmDevicesSpec devicesSpec; private final List candidatePrimaryStorageUuidsForRootVolume = new ArrayList<>(); private final List candidatePrimaryStorageUuidsForDataVolume = new ArrayList<>(); @@ -384,4 +386,12 @@ public boolean getVirtio() { public void setVirtio(boolean virtio) { this.virtio = virtio; } + + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } } diff --git a/header/src/main/java/org/zstack/header/vm/DiskAO.java b/header/src/main/java/org/zstack/header/vm/DiskAO.java new file mode 100644 index 00000000000..3677ff749f3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/DiskAO.java @@ -0,0 +1,151 @@ +package org.zstack.header.vm; + +import org.zstack.header.configuration.PythonClassInventory; +import org.zstack.header.volume.VolumeVO; + +import java.util.List; + +@PythonClassInventory +public class DiskAO { + private boolean boot; + private String platform; + private String guestOsType; + private String architecture; + private String primaryStorageUuid; + private long size; + /** + * allow: ImageVO.uuid + */ + private String templateUuid; + private String diskOfferingUuid; + private String sourceType; + /** + * allow: VolumeVO.uuid + */ + private String sourceUuid; + private List systemTags; + private String name; + + public DiskAO withImage(String imageUuid) { + this.templateUuid = imageUuid; + return this; + } + + public DiskAO withVolume(String volumeUuid) { + this.sourceType = VolumeVO.class.getSimpleName(); + this.sourceUuid = volumeUuid; + return this; + } + + public DiskAO withLun(String lunUuid) { + this.sourceType = "LunVO"; + this.sourceUuid = lunUuid; + return this; + } + + public boolean isBoot() { + return boot; + } + + public void setBoot(boolean boot) { + this.boot = boot; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getGuestOsType() { + return guestOsType; + } + + public void setGuestOsType(String guestOsType) { + this.guestOsType = guestOsType; + } + + public String getArchitecture() { + return architecture; + } + + public void setArchitecture(String architecture) { + this.architecture = architecture; + } + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public String getTemplateUuid() { + return templateUuid; + } + + public void setTemplateUuid(String templateUuid) { + this.templateUuid = templateUuid; + } + + public String getDiskOfferingUuid() { + return diskOfferingUuid; + } + + public void setDiskOfferingUuid(String diskOfferingUuid) { + this.diskOfferingUuid = diskOfferingUuid; + } + + public String getSourceType() { + return sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + public String getSourceUuid() { + return sourceUuid; + } + + public void setSourceUuid(String sourceUuid) { + this.sourceUuid = sourceUuid; + } + + public List getSystemTags() { + return systemTags; + } + + public void setSystemTags(List systemTags) { + this.systemTags = systemTags; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static DiskAO rootDisk() { + DiskAO disk = new DiskAO(); + disk.setBoot(true); + return disk; + } + + public static DiskAO nonRootDisk() { + return new DiskAO(); + } +} 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 e6fe6a92272..28c36d56d04 100755 --- a/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java +++ b/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java @@ -4,6 +4,7 @@ import org.zstack.header.errorcode.ErrorCode; import java.util.Date; import java.time.LocalDateTime; +import java.util.List; /** * Created by frank on 3/4/2016. @@ -20,6 +21,7 @@ public class VmCanonicalEvents { 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_GPU_STATUS_ABNORMAL = "/vm/gpu/status/abnormal"; + public static final String VM_HOST_FILE_CHANGED_PATH = "/vm/hostfile/change"; @NeedJsonSchema public static class VmCrashReportData { @@ -349,4 +351,44 @@ public void setStatus(String status) { this.status = status; } } + + @NeedJsonSchema + public static class VmHostFileChangedData { + private String hostUuid; + private String vmUuid; + private String type; + 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 String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + 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 61cfe494fab..46edc72606d 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java @@ -5,6 +5,7 @@ @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 c7df865bfa1..d06d7117ffe 100644 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java @@ -5,4 +5,10 @@ */ public interface VmInstanceCreateExtensionPoint { void preCreateVmInstance(CreateVmInstanceMsg msg); + + default void afterPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) { + } + + default void afterRollbackPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) { + } } diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMigrateExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMigrateExtensionPoint.java index 705483bd0a3..8792ca481d7 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceMigrateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMigrateExtensionPoint.java @@ -12,7 +12,7 @@ default void preMigrateVm(VmInstanceInventory inv, String destHostUuid, Completi completion.success(); } - void beforeMigrateVm(VmInstanceInventory inv, String destHostUuid); + default void beforeMigrateVm(VmInstanceInventory inv, String destHostUuid) {} default void postMigrateVm(VmInstanceInventory inv, String destHostUuid) {} 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 99ee2173b98..5d266f1ebc0 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java @@ -13,6 +13,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; @@ -372,6 +373,8 @@ public void setHostname(String hostname) { // if true, implementation will be required to offer debug info during operation private boolean debug; private VmCreationStrategy strategy; + private VmDevicesSpec devicesSpec = new VmDevicesSpec(); + private OperatingSystemBootingSpec osSpec = new OperatingSystemBootingSpec(); public List getRequiredClusterUuids() { return requiredClusterUuids; @@ -453,6 +456,22 @@ public boolean isUsbRedirect() { public void setUsbRedirect(boolean usbRedirect) { this.usbRedirect = usbRedirect; } + + 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 isEnableSecurityElement() { return enableSecurityElement; @@ -910,4 +929,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/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/VmHostBackupFileDeletionMsg.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileDeletionMsg.java new file mode 100644 index 00000000000..b386cc8ecf6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileDeletionMsg.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.DeletionMessage; + +public class VmHostBackupFileDeletionMsg extends DeletionMessage { + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileDeletionReply.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileDeletionReply.java new file mode 100644 index 00000000000..b87c233c4c1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileDeletionReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.MessageReply; + +public class VmHostBackupFileDeletionReply 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..2c3907cdd65 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO.java @@ -0,0 +1,77 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.vo.EntityGraph; +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/VmHostFileDeletionMsg.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileDeletionMsg.java new file mode 100644 index 00000000000..0b4a7499ad1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileDeletionMsg.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.DeletionMessage; + +public class VmHostFileDeletionMsg extends DeletionMessage { + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileDeletionReply.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileDeletionReply.java new file mode 100644 index 00000000000..56f22079798 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileDeletionReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.MessageReply; + +public class VmHostFileDeletionReply extends MessageReply { +} 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..6da9be52fc4 --- /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.uuidForAPIDoc()); + ref.setVmInstanceUuid(DocUtils.uuidForAPIDoc()); + ref.setHostUuid(DocUtils.uuidForAPIDoc()); + ref.setType(VmHostFileType.TpmState.toString()); + ref.setPath("/var/lib/libvirt/swtpm/" + ref.getHostUuid() + "/"); + ref.setLastSyncReason("on libvirt shutdown event"); + ref.setCreateDate(new Timestamp(DocUtils.date)); + ref.setLastOpDate(new Timestamp(DocUtils.date)); + 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..aa8b54cf7b6 --- /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.5.28" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.5.28" + } + field { + name "hostUuid" + desc "主机 UUID" + type "String" + since "5.5.28" + } + field { + name "type" + desc "文件类型, 按用途分类, 可能是 NvRam 或者 TpmState" + type "String" + since "5.5.28" + } + field { + name "path" + desc "主机侧相关文件或目录的路径" + type "String" + since "5.5.28" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.28" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.28" + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileManager.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileManager.java new file mode 100644 index 00000000000..342e4fe3d3a --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileManager.java @@ -0,0 +1,5 @@ +package org.zstack.header.vm.additions; + +public interface VmHostFileManager { + void cleanVmHostBackupFile(String resourceUuid); +} 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..4df6f0944bc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java @@ -0,0 +1,39 @@ +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"), + PreMigration("on pre-migration (from src host)"), + 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"), + ConventToTemplatedVM("on convent to templated VM"), + ; + + 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/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/PackageInfo.java b/header/src/main/java/org/zstack/header/vm/devices/PackageInfo.java new file mode 100644 index 00000000000..5ec4014942f --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/devices/PackageInfo.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm.devices; + +import org.zstack.header.rest.SDKPackage; + +@SDKPackage(packageName = "org.zstack.sdk") +public class PackageInfo { +} 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..be92d800e98 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/devices/VmDevicesSpec.java @@ -0,0 +1,35 @@ +package org.zstack.header.vm.devices; + +import org.zstack.header.tpm.entity.TpmSpec; +import org.zstack.header.rest.SDK; + +@SDK +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/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..03f5047f24e --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/HostKeyIdentityHelper.java @@ -0,0 +1,121 @@ +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.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 org.zstack.utils.logging.CLoggerImpl; + +import javax.persistence.EntityExistsException; +import javax.persistence.PersistenceException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.SQLIntegrityConstraintViolationException; +import java.sql.Timestamp; +import java.util.Base64; + +public final class HostKeyIdentityHelper { + private static final CLogger logger = CLoggerImpl.getLogger(HostKeyIdentityHelper.class); + + private HostKeyIdentityHelper() { + } + + public static String fingerprintFromPublicKey(String publicKeyBase64) { + if (StringUtils.isBlank(publicKeyBase64)) { + return ""; + } + try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64.trim()); + if (keyBytes.length == 0) { + return ""; + } + byte[] hash = MessageDigest.getInstance("SHA-256").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, SimpleQuery.Op.EQ, hostUuid); + return q.find(); + } + + 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 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); + } + + public static void setVerified(DatabaseFacade dbf, String hostUuid, boolean verified) { + HostKeyIdentityVO vo = getHostKeyIdentity(dbf, hostUuid); + if (vo != null) { + vo.setVerified(verified); + dbf.update(vo); + } + } + + public static boolean isRotateNeededGetError(String errorCode) { + 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..c05437c6432 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/HostSecretEnvelopeCryptoExtensionPoint.java @@ -0,0 +1,5 @@ +package org.zstack.kvm; + +public interface HostSecretEnvelopeCryptoExtensionPoint { + 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 5011d661659..8f84a69f004 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -14,6 +14,8 @@ import org.zstack.header.vm.*; import org.zstack.header.vm.devices.DeviceAddress; import org.zstack.header.vm.devices.VirtualDeviceInfo; +import org.zstack.header.vm.additions.VmHostFileBackupJob; +import org.zstack.kvm.tpm.TpmTO; import org.zstack.network.securitygroup.RuleTO; import org.zstack.network.securitygroup.SecurityGroupMembersTO; import org.zstack.network.securitygroup.VmNicSecurityTO; @@ -403,6 +405,187 @@ public static class PingCmd extends AgentCommand { public long kvmagentPhysicalMemoryUsageHardLimit; } + 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 { + 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 v) { this.encryptedDek = v; } + public String getVmUuid() { return vmUuid; } + public void setVmUuid(String v) { this.vmUuid = v; } + public String getPurpose() { return purpose; } + public void setPurpose(String v) { this.purpose = v; } + public Integer getKeyVersion() { return keyVersion; } + public void setKeyVersion(Integer v) { this.keyVersion = v; } + public String getUsageInstance() { return usageInstance; } + public void setUsageInstance(String v) { this.usageInstance = v; } + public String getSecretUuid() { return secretUuid; } + public void setSecretUuid(String v) { this.secretUuid = v; } + public String getDescription() { return description; } + public void setDescription(String v) { this.description = v; } + } + + public static class SecretHostDefineResponse extends AgentResponse { + private String secretUuid; + public String getSecretUuid() { return secretUuid; } + public void setSecretUuid(String v) { this.secretUuid = v; } + } + + 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 v) { this.vmUuid = v; } + public String getPurpose() { return purpose; } + public void setPurpose(String v) { this.purpose = v; } + public Integer getKeyVersion() { return keyVersion; } + public void setKeyVersion(Integer v) { this.keyVersion = v; } + public String getUsageInstance() { return usageInstance; } + public void setUsageInstance(String v) { this.usageInstance = v; } + } + + public static class SecretHostGetResponse extends AgentResponse { + public static final String ERROR_CODE_SECRET_NOT_FOUND = "KEY_AGENT_SECRET_NOT_FOUND"; + private String secretUuid; + private String errorCode; + public String getSecretUuid() { return secretUuid; } + public void setSecretUuid(String v) { this.secretUuid = v; } + public String getErrorCode() { return errorCode; } + public void setErrorCode(String v) { this.errorCode = v; } + } + + 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 v) { this.vmUuid = v; } + public String getPurpose() { return purpose; } + public void setPurpose(String v) { this.purpose = v; } + public Integer getKeyVersion() { return keyVersion; } + public void setKeyVersion(Integer v) { this.keyVersion = v; } + public String getUsageInstance() { return usageInstance; } + public void setUsageInstance(String v) { this.usageInstance = v; } + } + + public static class SecretHostDeleteResponse extends AgentResponse {} + + public static class ResolveVtpmLibvirtSecretCmd extends AgentCommand { + private String vmUuid; + public String getVmUuid() { return vmUuid; } + public void setVmUuid(String v) { this.vmUuid = v; } + } + + public static class ResolveVtpmLibvirtSecretResponse extends AgentResponse { + private String secretUuid; + public String getSecretUuid() { return secretUuid; } + public void setSecretUuid(String v) { this.secretUuid = v; } + } + + public static class VmHostFileTO { + private String path; + private String type; + private String fileFormat; + private String operation; + @NoLogging(type = NoLogging.Type.LongText) + private String contentBase64; + private String error; + + public String getPath() { return path; } + public void setPath(String v) { this.path = v; } + public String getType() { return type; } + public void setType(String v) { this.type = v; } + public String getFileFormat() { return fileFormat; } + public void setFileFormat(String v) { this.fileFormat = v; } + public String getOperation() { return operation; } + public void setOperation(String v) { this.operation = v; } + public String getContentBase64() { return contentBase64; } + public void setContentBase64(String v) { this.contentBase64 = v; } + public String getError() { return error; } + public void setError(String v) { this.error = v; } + } + + public static class ReadVmHostFileContentCmd extends AgentCommand { + private List hostFiles = new ArrayList<>(); + public List getHostFiles() { return hostFiles; } + public void setHostFiles(List v) { this.hostFiles = v; } + public List getPaths() { + return hostFiles.stream().map(VmHostFileTO::getPath).collect(Collectors.toList()); + } + public void setPaths(List paths) { + hostFiles = paths.stream().map(path -> { + VmHostFileTO to = new VmHostFileTO(); + to.setPath(path); + return to; + }).collect(Collectors.toList()); + } + } + + public static class ReadVmHostFileContentResponse extends AgentResponse { + private List hostFiles = new ArrayList<>(); + public List getHostFiles() { return hostFiles; } + public void setHostFiles(List v) { this.hostFiles = v; } + } + + public static class WriteVmHostFileContentCmd extends AgentCommand { + private List hostFiles = new ArrayList<>(); + public List getHostFiles() { return hostFiles; } + public void setHostFiles(List v) { this.hostFiles = v; } + } + + public static class WriteVmHostFileContentResponse extends AgentResponse {} + + public static class BackupVmHostFileCmd extends AgentCommand { + private List vmHostFileBackupJobs; + public List getVmHostFileBackupJobs() { return vmHostFileBackupJobs; } + public void setVmHostFileBackupJobs(List v) { this.vmHostFileBackupJobs = v; } + } + + public static class BackupVmHostFileResponse extends AgentResponse {} + public static class PingResponse extends AgentResponse { private String hostUuid; private String sendCommandUrl; @@ -2393,9 +2576,13 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd { @GrayVersion(value = "5.0.0") private boolean secureBoot; @GrayVersion(value = "5.0.0") + private String edkVersion; + @GrayVersion(value = "5.0.0") private boolean fromForeignHypervisor; @GrayVersion(value = "5.0.0") private String machineType; + private VolumeTO nvRam; + private TpmTO tpm; @GrayVersion(value = "5.0.0") private Integer pciePortNums; @GrayVersion(value = "5.0.0") @@ -2524,6 +2711,11 @@ public void setMachineType(String machineType) { this.machineType = machineType; } + public VolumeTO getNvRam() { return nvRam; } + public void setNvRam(VolumeTO nvRam) { this.nvRam = nvRam; } + public TpmTO getTpm() { return tpm; } + public void setTpm(TpmTO tpm) { this.tpm = tpm; } + public void setPciePortNums(Integer pciePortNums) { this.pciePortNums = pciePortNums; } @@ -2556,6 +2748,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; } 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 c45ab09f616..8f9ddebfe34 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -51,6 +51,26 @@ public interface KVMConstant { String KVM_SYNC_VM_DEVICEINFO_PATH = "/sync/vm/deviceinfo"; String KVM_START_VM_PATH = "/vm/start"; String KVM_STOP_VM_PATH = "/vm/stop"; + + // vTPM / KMS / host file paths + 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 KVM_VTPM_RESOLVE_LIBVIRT_SECRET_UUID_PATH = "/vm/vtpm/resolveLibvirtSecretUuid"; + String KVM_REPORT_VM_HOST_FILE_CHANGED = "/kvm/reporthostfilechanged"; + 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"; + long ENVELOPE_KEY_HTTP_TIMEOUT_SEC = 10L; + int MAX_DEK_BYTES = 1024; + String HOST_SECRET_USAGE_INSTANCE_VTPM = "tpm0"; + String NV_RAM_FILE_PATH_FORMAT = "/var/lib/libvirt/qemu/nvram/%s-host-files/%s.fd"; + String TPM_STATE_FILE_PATH_FORMAT = "/var/lib/libvirt/swtpm/%s/"; + String EDK_VERSION_NONE = "None"; String KVM_PAUSE_VM_PATH = "/vm/pause"; String KVM_RESUME_VM_PATH = "/vm/resume"; String KVM_REBOOT_VM_PATH = "/vm/reboot"; 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 d2b2063b635..c0d4f24dfa3 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 @@ -157,4 +159,19 @@ public class KVMGlobalConfig { @BindResourceConfig({HostVO.class, ClusterVO.class}) public static GlobalConfig MINIMUM_MEMORY_SIZE_BEFORE_START_VM = new GlobalConfig(CATEGORY, "min.free.memory.size"); + @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 f480874e526..76d21169b85 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -73,6 +73,7 @@ import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.rest.JsonAsyncRESTCallback; import org.zstack.header.rest.RESTFacade; +import org.zstack.header.secret.*; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.*; import org.zstack.header.tag.SystemTagInventory; @@ -244,6 +245,10 @@ public static Set parseSanIps(String sanOutput) { private String vmFstrimPath; private String blockCommitPath; private String blockPullPath; + private String getSecretPath; + private String ensureSecretPath; + private String deleteSecretPath; + private String vtpmResolveLibvirtSecretPath; private String agentPackageName = KVMGlobalProperty.AGENT_PACKAGE_NAME; private String hostTakeOverFlagPath = KVMGlobalProperty.TAKEVOERFLAGPATH; @@ -472,6 +477,22 @@ public KVMHost(KVMHostVO self, KVMHostContext context) { ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_BLOCK_PULL_VOLUME_PATH); blockPullPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.KVM_GET_SECRET_PATH); + getSecretPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.KVM_ENSURE_SECRET_PATH); + ensureSecretPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.KVM_DELETE_SECRET_PATH); + deleteSecretPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.KVM_VTPM_RESOLVE_LIBVIRT_SECRET_UUID_PATH); + vtpmResolveLibvirtSecretPath = ub.build().toString(); } static { @@ -652,6 +673,14 @@ protected void handleLocalMessage(Message msg) { handle((KVMHostAsyncHttpCallMsg) msg); } else if (msg instanceof KVMHostSyncHttpCallMsg) { handle((KVMHostSyncHttpCallMsg) 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 if (msg instanceof ResolveVtpmLibvirtSecretOnHypervisorMsg) { + handle((ResolveVtpmLibvirtSecretOnHypervisorMsg) msg); } else if (msg instanceof DetachNicFromVmOnHypervisorMsg) { handle((DetachNicFromVmOnHypervisorMsg) msg); } else if (msg instanceof ChangeVmNicStateOnHypervisorMsg) { @@ -737,6 +766,200 @@ protected void handleLocalMessage(Message msg) { } } + + private long envelopeKeyHttpTimeoutMillis() { + return TimeUnit.SECONDS.toMillis(KVMConstant.ENVELOPE_KEY_HTTP_TIMEOUT_SEC); + } + + private void handle(SecretHostGetMsg msg) { + checkStatus(); + + SecretHostGetReply reply = new SecretHostGetReply(); + SecretHostGetCmd cmd = new SecretHostGetCmd(); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setUsageInstance(msg.getUsageInstance()); + + new Http<>(getSecretPath, cmd, SecretHostGetResponse.class) + .timeout(envelopeKeyHttpTimeoutMillis()) + .call(new ReturnValueCompletion(msg) { + @Override + public void success(SecretHostGetResponse ret) { + if (!ret.isSuccess()) { + String details = StringUtils.defaultIfBlank(ret.getError(), "get host secret failed"); + if (SecretHostGetResponse.ERROR_CODE_SECRET_NOT_FOUND.equals(ret.getErrorCode()) + || details.contains(SecretHostGetResponse.ERROR_CODE_SECRET_NOT_FOUND)) { + reply.setError(operr(SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND, "%s", details)); + } else { + reply.setError(operr(ORG_ZSTACK_KVM_10058, "failed to get host secret on host[uuid:%s], because:%s", self.getUuid(), details)); + } + } else { + reply.setSecretUuid(ret.getSecretUuid()); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + private void handle(SecretHostDefineMsg msg) { + checkStatus(); + + SecretHostDefineReply reply = new SecretHostDefineReply(); + SecretHostDefineCmd cmd; + try { + cmd = buildSecretHostDefineCmd(msg); + } catch (OperationFailureException e) { + reply.setError(e.getErrorCode()); + bus.reply(msg, reply); + return; + } + + new Http<>(ensureSecretPath, cmd, SecretHostDefineResponse.class) + .timeout(envelopeKeyHttpTimeoutMillis()) + .call(new ReturnValueCompletion(msg) { + @Override + public void success(SecretHostDefineResponse ret) { + if (!ret.isSuccess()) { + String details = StringUtils.defaultIfBlank(ret.getError(), "ensure host secret failed"); + if (HostKeyIdentityHelper.isRotateNeededGetError(details)) { + HostKeyIdentityHelper.setVerified(dbf, msg.getHostUuid(), false); + } + reply.setError(operr(ORG_ZSTACK_KVM_10058, "failed to define host secret on host[uuid:%s], because:%s", self.getUuid(), details)); + } else { + reply.setSecretUuid(ret.getSecretUuid()); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + private SecretHostDefineCmd buildSecretHostDefineCmd(SecretHostDefineMsg msg) { + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) + || msg.getKeyVersion() == null || StringUtils.isBlank(msg.getDekBase64())) { + throw new OperationFailureException(operr("vmUuid, purpose, keyVersion and dekBase64 are required to define host secret")); + } + + HostKeyIdentityVO identity = HostKeyIdentityHelper.getHostKeyIdentity(dbf, msg.getHostUuid()); + if (identity == null || !Boolean.TRUE.equals(identity.getVerified()) || StringUtils.isBlank(identity.getPublicKey())) { + throw new OperationFailureException(operr("host[uuid:%s] envelope key identity is not verified", msg.getHostUuid())); + } + + String computed = HostKeyIdentityHelper.fingerprintFromPublicKey(identity.getPublicKey()); + if (StringUtils.isBlank(computed) || !StringUtils.equals(computed, identity.getFingerprint())) { + HostKeyIdentityHelper.setVerified(dbf, msg.getHostUuid(), false); + throw new OperationFailureException(operr("host[uuid:%s] envelope key fingerprint mismatch", msg.getHostUuid())); + } + + byte[] dek; + byte[] publicKey; + try { + dek = Base64.getDecoder().decode(msg.getDekBase64()); + publicKey = Base64.getDecoder().decode(identity.getPublicKey().trim()); + } catch (IllegalArgumentException e) { + throw new OperationFailureException(operr("invalid base64 host secret input for host[uuid:%s]", msg.getHostUuid())); + } + if (dek.length == 0 || dek.length > KVMConstant.MAX_DEK_BYTES) { + throw new OperationFailureException(operr("invalid DEK length[%s] for host[uuid:%s]", dek.length, msg.getHostUuid())); + } + + List exts = pluginRegistry.getExtensionList(HostSecretEnvelopeCryptoExtensionPoint.class); + if (exts.isEmpty()) { + throw new OperationFailureException(operr("no HostSecretEnvelopeCryptoExtensionPoint registered")); + } + + byte[] encryptedDek; + try { + encryptedDek = exts.get(0).seal(publicKey, dek); + } catch (Exception e) { + throw new OperationFailureException(operr("failed to seal DEK for host[uuid:%s]", msg.getHostUuid()).withException(e.getMessage())); + } finally { + Arrays.fill(dek, (byte) 0); + } + + SecretHostDefineCmd cmd = new SecretHostDefineCmd(); + cmd.setEncryptedDek(Base64.getEncoder().encodeToString(encryptedDek)); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setUsageInstance(msg.getUsageInstance()); + cmd.setSecretUuid(msg.getSecretUuid()); + cmd.setDescription(msg.getDescription()); + return cmd; + } + + private void handle(SecretHostDeleteMsg msg) { + checkStatus(); + + SecretHostDeleteReply reply = new SecretHostDeleteReply(); + SecretHostDeleteCmd cmd = new SecretHostDeleteCmd(); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setUsageInstance(msg.getUsageInstance()); + + new Http<>(deleteSecretPath, cmd, SecretHostDeleteResponse.class) + .timeout(envelopeKeyHttpTimeoutMillis()) + .call(new ReturnValueCompletion(msg) { + @Override + public void success(SecretHostDeleteResponse ret) { + if (!ret.isSuccess()) { + reply.setError(operr(ORG_ZSTACK_KVM_10058, "failed to delete host secret on host[uuid:%s], because:%s", + self.getUuid(), ret.getError())); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + private void handle(ResolveVtpmLibvirtSecretOnHypervisorMsg msg) { + checkStatus(); + + ResolveVtpmLibvirtSecretOnHypervisorReply reply = new ResolveVtpmLibvirtSecretOnHypervisorReply(); + Map command = map(e("vmInstanceUuid", msg.getVmUuid())); + new Http<>(vtpmResolveLibvirtSecretPath, + JSONObjectUtil.toJsonString(command), + ResolveVtpmLibvirtSecretCmd.class.getName(), + ResolveVtpmLibvirtSecretResponse.class) + .timeout(envelopeKeyHttpTimeoutMillis()) + .call(new ReturnValueCompletion(msg) { + @Override + public void success(ResolveVtpmLibvirtSecretResponse ret) { + if (!ret.isSuccess()) { + reply.setError(operr(ORG_ZSTACK_KVM_10058, "failed to resolve vTPM libvirt secret on host[uuid:%s], because:%s", + self.getUuid(), ret.getError())); + } else { + reply.setSecretUuid(ret.getSecretUuid()); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + private void handle(RestartKvmAgentMsg msg) { RestartKvmAgentReply reply = new RestartKvmAgentReply(); thdf.singleFlightSubmit(new SingleFlightTask(msg) 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 e5c14fb9d57..c63c91f10fa 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java @@ -5,6 +5,7 @@ import org.zstack.header.network.l2.L2NetworkVO; import org.zstack.header.tag.TagDefinition; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO; import org.zstack.header.vm.VmNicVO; import org.zstack.header.volume.VolumeVO; import org.zstack.kvm.xmlhook.XmlHookVO; @@ -68,4 +69,16 @@ public class KVMSystemTags { public static final String LIBVIRT_CAPABILITIES_TOKEN = "libvirtCapabilities"; public static PatternedSystemTag LIBVIRT_CAPABILITIES = new PatternedSystemTag(String.format("libvirtCapabilities::{%s}", LIBVIRT_CAPABILITIES_TOKEN), 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..06782d4bab7 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java @@ -0,0 +1,899 @@ +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.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.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.*; +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, + VmPreMigrationExtensionPoint, + AfterReimageVmInstanceExtensionPoint, + VmReleaseResourceExtensionPoint, + VmInstanceMigrateExtensionPoint, + VolumeSnapshotCreationExtensionPoint, + BeforeHaStartVmInstanceExtensionPoint { + private static final CLogger logger = Utils.getLogger(KvmSecureBootExtensions.class); + + public static String buildNvramFilePath(String vmUuid) { + return String.format(NV_RAM_FILE_PATH_FORMAT, vmUuid, vmUuid); + } + + public static String buildTpmStateFilePath(String vmUuid) { + return String.format(TPM_STATE_FILE_PATH_FORMAT, hyphenatedUuid(vmUuid)); + } + + public static String buildNvramSnapshotBackupFilePath(String vmUuid) { + return buildNvramFilePath(vmUuid); + } + + public static String buildTpmStateSnapshotBackupFilePath(String vmUuid) { + return buildTpmStateFilePath(vmUuid); + } + + private static String hyphenatedUuid(String uuid) { + if (uuid == null || uuid.contains("-") || uuid.length() != 32) { + return uuid; + } + + return String.format("%s-%s-%s-%s-%s", + uuid.substring(0, 8), + uuid.substring(8, 12), + uuid.substring(12, 16), + uuid.substring(16, 20), + uuid.substring(20)); + } + + public static String buildPathForVmHostFileType(VmHostFileType type, String vmUuid) { + switch (type) { + case NvRam: + return buildNvramFilePath(vmUuid); + case TpmState: + return buildTpmStateFilePath(vmUuid); + default: + throw new IllegalArgumentException(String.format("unsupported VM host file type: %s", type)); + } + } + + @Autowired + private CloudBus bus; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private KvmVmHostFileFactory vmHostFileFactory; + @Autowired + private KvmSecureBootManager secureBootManager; + + 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("sync-vm-host-files-before-migration", trigger -> { + String vmUuid = vm.getUuid(); + + Tuple tuple = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vmUuid) + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) + .findTuple(); + String hostUuid = tuple.get(0, String.class); + if (hostUuid == null) { + hostUuid = tuple.get(1, String.class); + } + + List fileList = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .list(); + + if (fileList.isEmpty()) { + trigger.next(); + return; + } + + if (fileList.stream().allMatch(it -> it.getChangeDate() == null)) { + logger.debug(String.format("skip syncing VM host files before migration for VM[uuid=%s], " + + "All files are clean (changeDate is null)", vmUuid)); + trigger.next(); + return; + } + + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(hostUuid); + syncMsg.setVmUuid(vmUuid); + syncMsg.setSyncReason(PreMigration.reason()); + + for (VmHostFileVO file : fileList) { + if (file.getType() == 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(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + trigger.next(); + return; + } + trigger.fail(reply.getError()); + } + }); + }) + .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 syncFromOriginHostSuccess = false; + private boolean writeSuccess = false; + private VmHostFileVO vmHostFile; + private VmHostBackupFileVO vmBackupFileVO; + + // property: VmHostBackupFileVO (when "backupUuid" is set) + // > VmHostFileVO (read success) + // > VmHostFileVO (read fail, but has content) + // > 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 = context.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.syncFromOriginHostSuccess = true; + trigger.next(); + return; + } + + 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__ = "check-content-for-vm-host-file-if-sync-failed"; + + @Override + public boolean skip(Map data) { + return context.syncFromOriginHostSuccess || context.vmHostFile == null; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + Timestamp latestChangeDate = Q.New(VmHostFileVO.class) + .select(VmHostFileVO_.changeDate) + .eq(VmHostFileVO_.uuid, context.vmHostFile.getUuid()) + .findValue(); + if (latestChangeDate != null && !context.sameHost) { + trigger.fail(operr( + "vm host file content on management node may be stale: change is pending on host " + + "but sync from origin host failed, cannot use cached content[vmUuid=%s, type=%s]", + context.vmUuid, context.type)); + return; + } + + if (!Q.New(VmHostFileContentVO.class) + .eq(VmHostFileContentVO_.uuid, context.vmHostFile.getUuid()) + .isExists()) { + logger.debug(String.format( + "VmHostFileVO[vmUuid=%s, hostUuid=%s, type=%s] has no content", + context.vmUuid, context.hostUuid, context.type)); + context.vmHostFile = null; + } + 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 afterReimageVmInstance(VolumeInventory inventory) { + String vmUuid = inventory.getVmInstanceUuid(); + if (vmUuid == null) { + return; + } + + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .delete(); + secureBootManager.cleanVmHostBackupFile(vmUuid); + + 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..a4b424f03a5 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java @@ -0,0 +1,1153 @@ +package org.zstack.kvm.efi; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +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.VmInstanceState; +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.VmHostBackupFileDeletionMsg; +import org.zstack.header.vm.additions.VmHostBackupFileDeletionReply; +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.VmHostFileDeletionMsg; +import org.zstack.header.vm.additions.VmHostFileDeletionReply; +import org.zstack.header.vm.additions.VmHostFileManager; +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.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.efi.KvmSecureBootExtensions.buildPathForVmHostFileType; +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.*; + +public class KvmSecureBootManager extends AbstractService implements VmHostFileManager { + 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; + @Autowired + private TpmEncryptedResourceKeyBackend resourceKeyBackend; + + @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 if (msg instanceof VmHostFileDeletionMsg) { + handle((VmHostFileDeletionMsg) msg); + } else if (msg instanceof VmHostBackupFileDeletionMsg) { + handle((VmHostBackupFileDeletionMsg) 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()); + + 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() { + Set backupUuidSet = new HashSet<>(); + for (VmHostBackupFileVO bf : backupFilesToPersist) { + backupUuidSet.addAll(q(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, bf.getResourceUuid()) + .eq(VmHostBackupFileVO_.type, bf.getType()) + .select(VmHostBackupFileVO_.uuid) + .listValues()); + } + + if (!backupUuidSet.isEmpty()) { + // TODO: need refactor -> use VmHostBackupFileDeletionMsg + safeForEach(backupUuidSet, resourceKeyBackend::cleanEncryptedResourceKey); + sql(VmHostBackupFileVO.class) + .in(VmHostBackupFileVO_.uuid, backupUuidSet) + .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; + } + + VmInstanceState vmState = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, msg.getSrcVmUuid()) + .select(VmInstanceVO_.state) + .findValue(); + if (vmState == VmInstanceState.Stopped) { + boolean anyChange = context.files.stream() + .anyMatch(it -> it.getChangeDate() != null); + if (!anyChange) { + 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 == null || errorCodeList.isEmpty()) { + trigger.next(); + return; + } + String details = String.join("\n", transform(errorCodeList.getCauses(), ErrorCode::getReadableDetails)); + if (msg.isIgnoreSyncError()) { + logger.warn(String.format( + "failed to sync host file for VM[uuid=%s] but ignoreSyncError=true, continue:\n%s", + msg.getSrcVmUuid(), details)); + trigger.next(); + } else { + trigger.fail(operr("failed to sync host file for VM[uuid=%s]", msg.getSrcVmUuid()) + .withCause(errorCodeList)); + } + } + }); + } + }).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(VmHostBackupFileVO_.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() { + Set backupUuidSet = new HashSet<>(); + for (VmHostBackupFileVO bf : filesNeedPersists) { + backupUuidSet.addAll(q(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, bf.getResourceUuid()) + .eq(VmHostBackupFileVO_.type, bf.getType()) + .select(VmHostBackupFileVO_.uuid) + .listValues()); + } + + if (!backupUuidSet.isEmpty()) { + // TODO: need refactor -> use VmHostBackupFileDeletionMsg + safeForEach(backupUuidSet, resourceKeyBackend::cleanEncryptedResourceKey); + sql(VmHostBackupFileVO.class) + .in(VmHostBackupFileVO_.uuid, backupUuidSet) + .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); + } + }); + } + + @Override + public void cleanVmHostBackupFile(String resourceUuid) { + List backups = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, resourceUuid) + .list(); + + for (VmHostBackupFileVO backup : backups) { + try { + vmHostFileFactory.createBackupBase(backup).clean(); + } catch (Exception e) { + logger.warn(String.format("failed to delete VmHostBackupFileVO[uuid:%s, type:%s]: %s", + backup.getUuid(), backup.getType(), e.getMessage()), e); + } + } + } + + private void handle(VmHostFileDeletionMsg msg) { + VmHostFileDeletionReply reply = new VmHostFileDeletionReply(); + databaseFacade.removeByPrimaryKey(msg.getUuid(), VmHostFileVO.class); + // VmHostFileContentVO will be deleted cascade + bus.reply(msg, reply); + } + + private void handle(VmHostBackupFileDeletionMsg msg) { + VmHostBackupFileDeletionReply reply = new VmHostBackupFileDeletionReply(); + VmHostBackupFileVO backup = databaseFacade.findByUuid(msg.getUuid(), VmHostBackupFileVO.class); + if (backup != null) { + try { + vmHostFileFactory.createBackupBase(backup).clean(); + } catch (Exception e) { + if (msg.isForceDelete()) { + logger.warn(String.format("force delete VmHostBackupFile[uuid:%s] ignored cleanup error: %s", + backup.getUuid(), e.getMessage()), e); + } else { + reply.setError(operr("failed to delete VmHostBackupFile[uuid:%s]", backup.getUuid()) + .withException(e)); + } + } + } + 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..f27ea6f52c5 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java @@ -0,0 +1,48 @@ +package org.zstack.kvm.efi; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.header.errorcode.OperationFailureException; +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 new OperationFailureException(operr("invalid VM host file type: " + file.getType())); + } + } + + public AbstractVmHostBackupFileBase createBackupBase(VmHostBackupFileVO backupFile) { + switch (backupFile.getType()) { + case NvRam: return new NvRamVmHostBackupFileBase(backupFile); + case TpmState: return new TpmStateVmHostBackupFileBase(backupFile); + default: throw new OperationFailureException(operr("invalid VM host file type: " + backupFile.getType())); + } + } + + 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..99f75ad6841 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java @@ -0,0 +1,893 @@ +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.secret.SecretHostDefineReply; +import org.zstack.header.secret.SecretHostGetMsg; +import org.zstack.header.secret.SecretHostGetReply; +import org.zstack.header.secret.ResolveVtpmLibvirtSecretOnHypervisorMsg; +import org.zstack.header.secret.ResolveVtpmLibvirtSecretOnHypervisorReply; +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.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.tpm.message.TpmDeletionMsg; +import org.zstack.header.vm.HaStartVmInstanceMsg; +import org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint; +import org.zstack.header.vm.VmAfterExpungeExtensionPoint; +import org.zstack.header.vm.VmJustBeforeDeleteFromDbExtensionPoint; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceMigrateExtensionPoint; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.VmInstantiateResourceException; +import org.zstack.header.vm.VmStateChangedExtensionPoint; +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.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.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.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static org.zstack.header.tpm.TpmConstants.SERVICE_ID; +import static org.zstack.kvm.KVMConstant.*; +import static org.zstack.kvm.efi.KvmSecureBootExtensions.*; +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); + if (StringUtils.isBlank(context.providerUuid) && StringUtils.isNotBlank(tpmSpec.getKeyProviderUuid())) { + int updated = resourceKeyBackend.applyKeyProviderWithKek(context.tpmUuid, tpmSpec.getKeyProviderUuid()); + if (updated > 0) { + context.providerUuid = tpmSpec.getKeyProviderUuid(); + logger.info(String.format( + "auto repaired TPM key provider binding for tpm[uuid:%s], providerUuid:%s", + context.tpmUuid, context.providerUuid)); + } else if (!resourceKeyBackend.checkTpmKeyProviderAttached(context.tpmUuid)) { + context.providerUuid = tpmSpec.getKeyProviderUuid(); + resourceKeyBackend.attachKeyProviderToTpm(context.tpmUuid, context.providerUuid); + logger.info(String.format( + "auto repaired TPM key provider binding for tpm[uuid:%s], provider[uuid:%s]", + context.tpmUuid, context.providerUuid)); + } else { + logger.warn(String.format( + "failed in-place providerUuid repair in preInstantiate for tpm[uuid:%s], providerUuid:%s, existing ref rows remain", + context.tpmUuid, tpmSpec.getKeyProviderUuid())); + } + } + 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; + } + + // 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(); + if (StringUtils.isBlank(r.getSecretUuid())) { + logger.warn(String.format( + "get secret on host succeeded but returned empty secretUuid for tpm[uuid:%s], host[uuid:%s], recreate DEK from KMS", + context.tpmUuid, spec.getDestHost().getUuid())); + trigger.next(); + return; + } + spec.getDevicesSpec().getTpm().setSecretUuid(r.getSecretUuid()); + context.vtpmSecretAlreadyOnHost = true; + 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 || context.vtpmSecretAlreadyOnHost; + } + + @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 || context.vtpmSecretAlreadyOnHost; + } + + @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; + boolean vtpmSecretAlreadyOnHost; + + 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"); + } + + TpmDeletionMsg removeMsg = new TpmDeletionMsg(); + removeMsg.setVmInstanceUuid(inv.getUuid()); + removeMsg.setTpmUuid(tpmUuid); + removeMsg.setForceDelete(true); + 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; + } + VtpmMigratePreAgentContext ctx = new VtpmMigratePreAgentContext(inv.getUuid(), srcHostUuid, destHostUuid); + ctx.setEnableKeyProvider(!VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class)); + String tpmUuid = VmTpmManager.findTpmUuidForVmOrNull(ctx.getVmUuid()); + if (StringUtils.isBlank(tpmUuid)) { + completion.success(); + return; + } + if (!ctx.isEnableKeyProvider()) { + completion.success(); + return; + } + try { + ctx.setTpmUuid(tpmUuid); + ensureVtpmKeyBindingAndDekLoaded(ctx); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + return; + } + resolveSourceHostVtpmSecretUuid(ctx, new ReturnValueCompletion(completion) { + @Override + public void success(String sourceSecretUuid) { + ctx.setSourceSecretUuid(sourceSecretUuid); + ensureDestinationHostVtpmSecretDefined(ctx, new Completion(completion) { + @Override + public void success() { + completion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + private void ensureVtpmKeyBindingAndDekLoaded(VtpmMigratePreAgentContext ctx) { + String tpmUuid = ctx.getTpmUuid(); + 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(tpmUuid); + 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", tpmUuid)); + } + ctx.setResourceKeyResult(result); + } + + private void resolveSourceHostVtpmSecretUuid(VtpmMigratePreAgentContext ctx, ReturnValueCompletion completion) { + ResolveVtpmLibvirtSecretOnHypervisorMsg m = new ResolveVtpmLibvirtSecretOnHypervisorMsg(); + m.setHostUuid(ctx.getSrcHostUuid()); + m.setVmUuid(ctx.getVmUuid()); + bus.makeTargetServiceIdByResourceUuid(m, HostConstant.SERVICE_ID, m.getHostUuid()); + bus.send(m, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply r) { + if (!r.isSuccess()) { + logger.warn(String.format("vTPM resolve libvirt secret uuid on agent failed, vmUuid:%s, err:%s", + ctx.getVmUuid(), r.getError().getReadableDetails())); + completion.fail(operr( + "cannot continue vTPM migrate precheck: failed to resolve libvirt secret UUID from domain XML, vmUuid:%s, srcHostUuid:%s", + ctx.getVmUuid(), ctx.getSrcHostUuid())); + return; + } + ResolveVtpmLibvirtSecretOnHypervisorReply rr = r.castReply(); + String sourceSecretUuid = StringUtils.isNotBlank(rr.getSecretUuid()) ? rr.getSecretUuid() : null; + if (sourceSecretUuid == null) { + completion.fail(operr( + "cannot continue vTPM migrate precheck: failed to resolve libvirt secret UUID from domain XML, vmUuid:%s, srcHostUuid:%s", + ctx.getVmUuid(), ctx.getSrcHostUuid())); + return; + } + logger.info(String.format( + "vTPM preMigrate source secret uuid resolved from domain XML, vmUuid:%s, srcHostUuid:%s, xmlHint:%s", + ctx.getVmUuid(), ctx.getSrcHostUuid(), sourceSecretUuid)); + completion.success(sourceSecretUuid); + } + }); + } + + private void ensureDestinationHostVtpmSecretDefined(VtpmMigratePreAgentContext ctx, Completion completion) { + ResourceKeyResult keyResult = ctx.getResourceKeyResult(); + if (keyResult == null || StringUtils.isBlank(keyResult.getDekBase64())) { + completion.fail(operr("missing DEK for tpm[uuid:%s] before ensure secret on destination", ctx.getTpmUuid())); + return; + } + + 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()); + bus.send(defMsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply defReply) { + if (!defReply.isSuccess()) { + completion.fail(defReply.getError()); + return; + } + + SecretHostDefineReply defR = defReply.castReply(); + if (StringUtils.isBlank(defR.getSecretUuid())) { + completion.fail(operr( + "define secret on host succeeded but returned empty secretUuid, hostUuid:%s", ctx.getDstHostUuid())); + return; + } + String destSecretUuid = defR.getSecretUuid(); + logger.info(String.format( + "vTPM preMigrate destination secret defined, vmUuid:%s, dstHostUuid:%s, secretUuid:%s", + ctx.getVmUuid(), ctx.getDstHostUuid(), destSecretUuid)); + completion.success(); + } + }); + } + + @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 failedToMigrateVm(VmInstanceInventory inv, String destHostUuid, ErrorCode reason, NoErrorCompletion completion) { + String vmUuid = inv == null ? null : inv.getUuid(); + if (StringUtils.isBlank(vmUuid) || StringUtils.isBlank(destHostUuid)) { + completion.done(); + return; + } + + Integer keyVersion = findTpmKeyVersionByVmUuid(vmUuid); + deleteHostSecretBestEffort(destHostUuid, vmUuid, keyVersion, "failed-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..8335b90afc6 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java @@ -0,0 +1,1074 @@ +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.Platform; +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.DatabaseFacade; +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.FlowRollback; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.OperationFailureException; +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.keyprovider.EncryptedResourceKeyManager; +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.TpmDeletionMsg; +import org.zstack.header.tpm.message.TpmDeletionReply; +import org.zstack.header.vm.VmInstanceConstant; +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.VmHostBackupFileDeletionMsg; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileDeletionMsg; +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.header.tpm.message.BackupTpmEncryptionKeyMsg; +import org.zstack.header.tpm.message.BackupTpmEncryptionKeyReply; +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.compute.vm.devices.TpmEncryptedResourceKeyBackend.*; +import static org.zstack.core.Platform.err; +import static org.zstack.core.Platform.operr; +import static org.zstack.header.errorcode.SysErrors.OPERATION_ERROR; +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.isEmpty; +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 EncryptedResourceKeyManager resourceKeyManager; + @Autowired + private KvmSecureBootExtensions secureBootExtensions; + @Autowired + private DatabaseFacade databaseFacade; + @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 TpmDeletionMsg) { + handle((TpmDeletionMsg) msg); + } else if (msg instanceof CloneVmTpmMsg) { + handle((CloneVmTpmMsg) msg); + } else if (msg instanceof BackupTpmEncryptionKeyMsg) { + handle((BackupTpmEncryptionKeyMsg) 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) { + if (msg.getKeyProviderUuid() != null && msg.getResourceUuidKeyFrom() != null) { + throw new OperationFailureException(operr("keyProviderUuid and resourceUuidKeyFrom cannot be set at the same time")); + } + + 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; // create new key with the provider uuid + String resourceUuidKeyFrom; // copy key from the resource uuid + String vmInstanceUuid; + String tpmUuid; + + boolean tpmCreated; + boolean keyProviderAttached; + boolean resourceKeyCreateAttempted; + String createdTpmUuid; + EncryptedResourceKeyManager.ResourceKeyResult resourceKeyResult; + + static AddTpmToVmContext valueOf(AddTpmMsg msg) { + AddTpmToVmContext context = new AddTpmToVmContext(); + context.keyProviderUuid = msg.getKeyProviderUuid(); + context.resourceUuidKeyFrom = msg.getResourceUuidKeyFrom(); + 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) || context.keyProviderUuid == null) + .handle(trigger -> { + tpmKeyBackend.attachKeyProviderToTpm(context.createdTpmUuid, context.keyProviderUuid); + context.keyProviderAttached = true; + + EncryptedResourceKeyManager.GetOrCreateResourceKeyContext keyCtx = + new EncryptedResourceKeyManager.GetOrCreateResourceKeyContext(); + keyCtx.setResourceUuid(context.createdTpmUuid); + keyCtx.setResourceType(TpmVO.class.getSimpleName()); + keyCtx.setKeyProviderUuid(context.keyProviderUuid); + keyCtx.setPurpose("vtpm"); + context.resourceKeyCreateAttempted = true; + resourceKeyManager.getOrCreateKey(keyCtx, new ReturnValueCompletion(trigger) { + @Override + public void success(EncryptedResourceKeyManager.ResourceKeyResult returnValue) { + context.resourceKeyResult = returnValue; + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + }) + .rollback(trigger -> { + if (context.keyProviderAttached && context.createdTpmUuid != null) { + rollbackKeyProviderAttachment(context, trigger); + return; + } + trigger.rollback(); + }) + .build()) + .then(Flow.of("copy-key-to-tpm") + .skipIf(data -> VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class) || context.resourceUuidKeyFrom == null) + .handle(trigger -> { + RestoreEncryptedResourceKeyContext restoreContext = new RestoreEncryptedResourceKeyContext(); + restoreContext.srcResourceUuid = context.resourceUuidKeyFrom; + restoreContext.dstResourceUuid = context.createdTpmUuid; + tpmKeyBackend.restoreEncryptedResourceKey(restoreContext); + trigger.next(); + }) + .rollback(trigger -> { + if (context.createdTpmUuid != null) { + tpmKeyBackend.detachKeyProviderFromTpm(context.createdTpmUuid); + } + trigger.rollback(); + }) + .build()) + .propagateExceptionTo(completion) + .done(completion::success) + .error(completion::fail) + .start(); + } + + private void rollbackKeyProviderAttachment(AddTpmToVmContext context, FlowRollback trigger) { + if (!context.resourceKeyCreateAttempted) { + detachKeyProviderFromTpmQuietly(context.createdTpmUuid); + trigger.rollback(); + return; + } + + EncryptedResourceKeyManager.ResourceKeyResult result = context.resourceKeyResult; + if (result == null) { + result = new EncryptedResourceKeyManager.ResourceKeyResult(); + result.setResourceUuid(context.createdTpmUuid); + result.setResourceType(TpmVO.class.getSimpleName()); + result.setKeyProviderUuid(context.keyProviderUuid); + result.setCreatedNewKey(true); + } + + resourceKeyManager.rollbackCreatedKey(result, new Completion(trigger) { + @Override + public void success() { + detachKeyProviderFromTpmQuietly(context.createdTpmUuid); + trigger.rollback(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to rollback resource key for TPM[uuid:%s] while adding TPM to VM[uuid:%s]: %s", + context.createdTpmUuid, + context.vmInstanceUuid, + errorCode == null ? "" : errorCode.getDetails())); + detachKeyProviderFromTpmQuietly(context.createdTpmUuid); + trigger.rollback(); + } + }); + } + + private void detachKeyProviderFromTpmQuietly(String tpmUuid) { + try { + tpmKeyBackend.detachKeyProviderFromTpm(tpmUuid); + } catch (Exception e) { + logger.warn(String.format("failed to detach key provider ref for TPM[uuid:%s]: %s", + tpmUuid, e.getMessage())); + } + } + + private void handle(TpmDeletionMsg msg) { + TpmDeletionReply reply = new TpmDeletionReply(); + 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(TpmDeletionMsg msg) { + RemoveTpmFromVmContext context = new RemoveTpmFromVmContext(); + context.vmInstanceUuid = msg.getVmInstanceUuid(); + context.tpmUuid = msg.getTpmUuid(); + context.force = msg.isForceDelete(); + 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); + List backupUuidList = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, context.vmInstanceUuid) + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) + .select(VmHostBackupFileVO_.uuid) + .listValues(); + for (String backupUuid : backupUuidList) { + tpmKeyBackend.cleanEncryptedResourceKey(backupUuid); + } + 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(); + } + + 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.of("clone-VM-TPM") + .then(Flow.of("persist-TPM-VO") + .handle(trigger -> { + reply.setInventories(new ArrayList<>()); + for (String dstVmUuid : msg.getDstVmUuidList()) { + TpmVO dstTpm = vmTpmManager.persistTpmVO(null, dstVmUuid); + reply.getInventories().add(TpmInventory.valueOf(dstTpm)); + } + trigger.next(); + }) + .rollback(trigger -> { + 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(); + } + }); + }) + .build()) + .then(Flow.of("clone-encrypted-resource-key-if-needed") + .handle(trigger -> { + 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)); + } + }); + }) + .build()) + .propagateExceptionTo(msg) + .done(() -> bus.reply(msg, reply)) + .error(errorCode -> { + reply.setError(errorCode); + bus.reply(msg, reply); + }) + .start(); + } + + private void handle(BackupTpmEncryptionKeyMsg msg) { + BackupEncryptedResourceKeyContext content = new BackupEncryptedResourceKeyContext(); + content.srcResourceUuid = msg.getSrcResourceUuid(); + content.dstResourceUuid = msg.getDstResourceUuid(); + tpmKeyBackend.backupEncryptedResourceKey(content); + bus.reply(msg, new BackupTpmEncryptionKeyReply()); + } + + 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-host-file-db-records-for-remains") + .skipIf(data -> context.hostFileToDeleteLast == null) + .handle(trigger -> { + VmHostFileDeletionMsg deletionMsg = new VmHostFileDeletionMsg(); + deletionMsg.setUuid(context.hostFileToDeleteLast.getUuid()); + deletionMsg.setForceDelete(false); + bus.makeLocalServiceId(deletionMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(deletionMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + trigger.next(); + } else { + trigger.fail(reply.getError()); + } + } + }); + }) + .build()) + .then(Flow.of("remove-backups-db-records-for-remains") + .handle(trigger -> { + List backupUuidList = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) + .select(VmHostBackupFileVO_.uuid) + .listValues(); + if (isEmpty(backupUuidList)) { + trigger.next(); + return; + } + + new While<>(backupUuidList).each((uuid, whileCompletion) -> { + VmHostBackupFileDeletionMsg deletionMsg = new VmHostBackupFileDeletionMsg(); + deletionMsg.setUuid(uuid); + // VmHostFileVO has been deleted in the previous step, so force delete here is safe + deletionMsg.setForceDelete(true); + bus.makeLocalServiceId(deletionMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(deletionMsg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + whileCompletion.addError(reply.getError()); + } + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (errorCodeList.hasError()) { + String details = String.join("\n", transform(errorCodeList.getCauses(), ErrorCode::getReadableDetails)); + logger.warn("failed to clean backup files but still continue:\n" + details); + } + 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()); + + TpmDeletionMsg inner = TpmDeletionMsg.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 new OperationFailureException(err(OPERATION_ERROR, "UpdateTpm is not supported in current version")); + } +} 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..39d5045dd76 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/SnapshotGroupRevertTpmHelper.java @@ -0,0 +1,138 @@ +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.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.core.db.Q; +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.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 TpmEncryptedResourceKeyBackend tpmKeyBackend; + + public void setupFromApi(APICreateVmInstanceFromVolumeSnapshotGroupMsg apiMsg, CreateVmInstanceMsg cmsg) { + String snapshotGroupUuid = apiMsg.getVolumeSnapshotGroupUuid(); + + boolean resetTpm = apiMsg.getResetTpm() == Boolean.TRUE; + 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); + } + setupTpmSpec(snapshotGroupUuid, tpmBackupFile, tpmSpec, cmsg, resetTpm); + } + + 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())); + } + } + + private void setupTpmSpec(String snapshotGroupUuid, VmHostBackupFileVO tpmBackupFile, TpmSpec tpmSpec, + CreateVmInstanceMsg cmsg, boolean resetTpm) { + 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) { + return; + } + + if (resetTpm) { + String defaultProviderUuid = tpmKeyBackend.defaultKeyProviderUuid(); + tpmSpec.setKeyProviderUuid(defaultProviderUuid); + logger.info(String.format( + "snapshot-reset TPM target provider selected, source[snapshotGroupUuid:%s,tpmBackupFileUuid:%s], " + + "destination[vmResourceUuid:%s,vmName:%s,providerUuid:%s]", + snapshotGroupUuid, tpmBackupFile.getUuid(), + cmsg.getResourceUuid(), cmsg.getName(), defaultProviderUuid)); + return; + } + + 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()); + } + return; + } + + 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())); + return; + } + + tpmSpec.setKeyProviderUuid(keyProviderUuid); + } +} 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..65be7cce42e --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostBackupFileBase.java @@ -0,0 +1,95 @@ +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.compute.vm.devices.TpmEncryptedResourceKeyBackend.BackupEncryptedResourceKeyContext; +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())); + + final BackupEncryptedResourceKeyContext context = new BackupEncryptedResourceKeyContext(); + context.srcResourceUuid = tpmUuid; + context.dstResourceUuid = self.getUuid(); + resourceKeyBackend.backupEncryptedResourceKey(context); + } + + @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(); + } + + @Override + public void clean() { + logger.debug(String.format("clean TpmState VmBackupFileVO[uuid=%s]", self.getUuid())); + resourceKeyBackend.cleanEncryptedResourceKey(self.getUuid()); + super.clean(); + } +} 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..d8968cb17b7 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmTO.java @@ -0,0 +1,16 @@ +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 v) { this.keyProviderUuid = v; } + public String getSecretUuid() { return secretUuid; } + public void setSecretUuid(String v) { this.secretUuid = v; } + public String getInstallPath() { return installPath; } + public void setInstallPath(String v) { this.installPath = v; } +} 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..36b14ce9ffe --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostBackupFileBase.java @@ -0,0 +1,34 @@ +package org.zstack.kvm.vmfiles; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.core.db.SQL; +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.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) { + } + + public void clean() { + SQL.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.uuid, self.getUuid()) + .delete(); + } +} 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/VmHostFileAllocatorExtensionPoint.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileAllocatorExtensionPoint.java new file mode 100644 index 00000000000..4a628cd3cbd --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileAllocatorExtensionPoint.java @@ -0,0 +1,63 @@ +package org.zstack.kvm.vmfiles; + +import org.zstack.header.allocator.HostAllocatorFilterExtensionPoint; +import org.zstack.header.allocator.HostAllocatorSpec; +import org.zstack.header.host.HostVO; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.core.db.Q; + +import com.google.common.base.Objects; + +import java.util.List; +import java.util.stream.Collectors; + +public class VmHostFileAllocatorExtensionPoint implements HostAllocatorFilterExtensionPoint { + private String filterErrorReason; + + @Override + public List filterHostCandidates(List candidates, HostAllocatorSpec spec) { + filterErrorReason = null; + if (spec == null) { + return candidates; + } + + String vmOperation = spec.getVmOperation(); + if (vmOperation == null || !VmInstanceConstant.VmOperation.Start.toString().equals(vmOperation)) { + return candidates; + } + + if (spec.getVmInstance() == null) { + return candidates; + } + + String vmUuid = spec.getVmInstance().getUuid(); + String lastHostUuid = spec.getVmInstance().getLastHostUuid(); + + List files = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, lastHostUuid) + .select(VmHostFileVO_.type) + .notNull(VmHostFileVO_.changeDate) + .listValues(); + if (files.isEmpty()) { + return candidates; + } + + String types = files.stream() + .map(Enum::name) + .collect(Collectors.joining(",")); + + filterErrorReason = String.format("only allowed start on last host: unsynchronized %s VM host files exist", types); + return candidates.stream() + .filter(host -> Objects.equal(lastHostUuid, host.getUuid())) + .collect(Collectors.toList()); + } + + @Override + public String filterErrorReason() { + return filterErrorReason; + } +} 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..3e7817edbd9 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileMsg.java @@ -0,0 +1,44 @@ +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; + private boolean ignoreSyncError = false; + + 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; + } + + public boolean isIgnoreSyncError() { + return ignoreSyncError; + } + + public void setIgnoreSyncError(boolean ignoreSyncError) { + this.ignoreSyncError = ignoreSyncError; + } +} 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/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 63756cc5fa5..135da2f5b1b 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -152,6 +152,9 @@ public class SourceClassMap { put("org.zstack.crypto.ccs.CCSCertificateInventory", "org.zstack.sdk.CCSCertificateInventory"); put("org.zstack.crypto.ccs.CCSCertificateUserRefInventory", "org.zstack.sdk.CCSCertificateUserRefInventory"); put("org.zstack.crypto.ccs.CCSCertificateUserState", "org.zstack.sdk.CCSCertificateUserState"); + 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.csp.CSPSecretResourcePoolInventory", "org.zstack.sdk.CSPSecretResourcePoolInventory"); put("org.zstack.crypto.securitymachine.thirdparty.fiSec.FiSecSecretResourcePoolInventory", "org.zstack.sdk.FiSecSecretResourcePoolInventory"); @@ -325,6 +328,12 @@ public class SourceClassMap { put("org.zstack.header.image.ImageGroupInventory", "org.zstack.sdk.ImageGroupInventory"); put("org.zstack.header.image.ImageGroupRefInventory", "org.zstack.sdk.ImageGroupRefInventory"); put("org.zstack.header.image.ImageInventory", "org.zstack.sdk.ImageInventory"); + put("org.zstack.header.keyprovider.CertificateInfo", "org.zstack.sdk.keyprovider.CertificateInfo"); + put("org.zstack.header.keyprovider.KeyProviderInventory", "org.zstack.sdk.keyprovider.KeyProviderInventory"); + put("org.zstack.header.keyprovider.KmsIdentityInventory", "org.zstack.sdk.keyprovider.KmsIdentityInventory"); + put("org.zstack.header.keyprovider.KmsInventory", "org.zstack.sdk.keyprovider.KmsInventory"); + put("org.zstack.header.keyprovider.NkpInventory", "org.zstack.sdk.keyprovider.NkpInventory"); + put("org.zstack.header.keyprovider.NkpRestoreInfo", "org.zstack.sdk.keyprovider.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"); @@ -414,6 +423,9 @@ 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.TpmCapabilityView"); + put("org.zstack.header.tpm.entity.TpmInventory", "org.zstack.sdk.tpm.TpmInventory"); + put("org.zstack.header.tpm.entity.TpmSpec", "org.zstack.sdk.tpm.TpmSpec"); put("org.zstack.header.vdpa.VmVdpaNicInventory", "org.zstack.sdk.VmVdpaNicInventory"); put("org.zstack.header.vipQos.VipQosInventory", "org.zstack.sdk.VipQosInventory"); put("org.zstack.header.vipQos.VpcSharedQosInventory", "org.zstack.sdk.VpcSharedQosInventory"); @@ -429,8 +441,11 @@ 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.NvRamSpec", "org.zstack.sdk.NvRamSpec"); + put("org.zstack.header.vm.devices.VmDevicesSpec", "org.zstack.sdk.VmDevicesSpec"); put("org.zstack.header.vm.devices.VmInstanceDeviceAddressArchiveInventory", "org.zstack.sdk.VmInstanceDeviceAddressArchiveInventory"); put("org.zstack.header.vm.devices.VmInstanceDeviceAddressGroupInventory", "org.zstack.sdk.VmInstanceDeviceAddressGroupInventory"); put("org.zstack.header.vmscheduling.HostSchedulingRuleGroupInventory", "org.zstack.sdk.HostSchedulingRuleGroupInventory"); @@ -1331,6 +1346,7 @@ public class SourceClassMap { put("org.zstack.sdk.NginxRedirectRule", "org.zstack.ai.NginxRedirectRule"); put("org.zstack.sdk.NicTO", "org.zstack.kvm.KVMAgentCommands$NicTO"); put("org.zstack.sdk.NormalIpRangeInventory", "org.zstack.header.network.l3.NormalIpRangeInventory"); + put("org.zstack.sdk.NvRamSpec", "org.zstack.header.vm.devices.NvRamSpec"); put("org.zstack.sdk.NvmeLunHostRefInventory", "org.zstack.storage.device.nvme.NvmeLunHostRefInventory"); put("org.zstack.sdk.NvmeLunInventory", "org.zstack.storage.device.nvme.NvmeLunInventory"); put("org.zstack.sdk.NvmeServerClusterRefInventory", "org.zstack.storage.device.nvme.NvmeServerClusterRefInventory"); @@ -1579,6 +1595,7 @@ public class SourceClassMap { put("org.zstack.sdk.VmCPUSpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmCPUSpendingDetails"); put("org.zstack.sdk.VmCapabilities", "org.zstack.header.vm.VmCapabilities"); put("org.zstack.sdk.VmCdRomInventory", "org.zstack.header.vm.cdrom.VmCdRomInventory"); + put("org.zstack.sdk.VmDevicesSpec", "org.zstack.header.vm.devices.VmDevicesSpec"); put("org.zstack.sdk.VmExternalBackupInfo", "org.zstack.externalbackup.VmExternalBackupInfo"); put("org.zstack.sdk.VmInstanceDeviceAddressArchiveInventory", "org.zstack.header.vm.devices.VmInstanceDeviceAddressArchiveInventory"); put("org.zstack.sdk.VmInstanceDeviceAddressGroupInventory", "org.zstack.header.vm.devices.VmInstanceDeviceAddressGroupInventory"); @@ -1704,6 +1721,15 @@ public class SourceClassMap { put("org.zstack.sdk.identity.role.RoleState", "org.zstack.header.identity.role.RoleState"); put("org.zstack.sdk.identity.role.RoleStateEvent", "org.zstack.header.identity.role.RoleStateEvent"); put("org.zstack.sdk.identity.role.RoleType", "org.zstack.header.identity.role.RoleType"); + put("org.zstack.sdk.keyprovider.CertificateInfo", "org.zstack.header.keyprovider.CertificateInfo"); + put("org.zstack.sdk.keyprovider.KeyProviderInventory", "org.zstack.header.keyprovider.KeyProviderInventory"); + put("org.zstack.sdk.keyprovider.KmsIdentityInventory", "org.zstack.header.keyprovider.KmsIdentityInventory"); + put("org.zstack.sdk.keyprovider.KmsInventory", "org.zstack.header.keyprovider.KmsInventory"); + put("org.zstack.sdk.keyprovider.NkpInventory", "org.zstack.header.keyprovider.NkpInventory"); + put("org.zstack.sdk.keyprovider.NkpRestoreInfo", "org.zstack.header.keyprovider.NkpRestoreInfo"); + 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.header.server.LicenseAuthorizedCapacityClientAddOnUsageView", "org.zstack.license.header.server.LicenseAuthorizedCapacityClientAddOnUsageView"); put("org.zstack.sdk.license.header.server.LicenseAuthorizedCapacityClientUsageView", "org.zstack.license.header.server.LicenseAuthorizedCapacityClientUsageView"); put("org.zstack.sdk.license.header.server.LicenseAuthorizedCapacityInventory", "org.zstack.license.header.server.LicenseAuthorizedCapacityInventory"); @@ -1752,6 +1778,10 @@ public class SourceClassMap { put("org.zstack.sdk.ticket.entity.TicketTypeInventory", "org.zstack.ticket.entity.TicketTypeInventory"); put("org.zstack.sdk.ticket.iam2.entity.IAM2TicketFlowCollectionInventory", "org.zstack.ticket.iam2.entity.IAM2TicketFlowCollectionInventory"); put("org.zstack.sdk.ticket.iam2.entity.IAM2TicketFlowInventory", "org.zstack.ticket.iam2.entity.IAM2TicketFlowInventory"); + put("org.zstack.sdk.tpm.TpmCapabilityView", "org.zstack.header.tpm.entity.TpmCapabilityView"); + put("org.zstack.sdk.tpm.TpmInventory", "org.zstack.header.tpm.entity.TpmInventory"); + put("org.zstack.sdk.tpm.TpmSpec", "org.zstack.header.tpm.entity.TpmSpec"); + put("org.zstack.sdk.vm.entity.VmHostFileInventory", "org.zstack.header.vm.additions.VmHostFileInventory"); put("org.zstack.sdk.zwatch.alarm.ActionParam", "org.zstack.zwatch.alarm.APICreateAlarmMsg$ActionParam"); put("org.zstack.sdk.zwatch.alarm.AlarmActionInventory", "org.zstack.zwatch.alarm.AlarmActionInventory"); put("org.zstack.sdk.zwatch.alarm.AlarmDataAckInventory", "org.zstack.zwatch.alarm.AlarmDataAckInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java b/sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java index c7ccc7e2c19..43d1e8f06d5 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java @@ -49,6 +49,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String vmNicParams; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public org.zstack.sdk.VmDevicesSpec devicesSpec; + @Param(required = false, validValues = {"UserVm","ApplianceVm"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String type; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java index a6c9c4cd2c4..651d7329fab 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java @@ -49,6 +49,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String vmNicParams; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public org.zstack.sdk.VmDevicesSpec devicesSpec; + @Param(required = false, validValues = {"UserVm","ApplianceVm"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String type; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java index 2ef24418b33..e1aab46b788 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java @@ -76,6 +76,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/NvRamSpec.java b/sdk/src/main/java/org/zstack/sdk/NvRamSpec.java new file mode 100644 index 00000000000..72172fcb0c1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/NvRamSpec.java @@ -0,0 +1,15 @@ +package org.zstack.sdk; + + + +public class NvRamSpec { + + public boolean needRegister; + public void setNeedRegister(boolean needRegister) { + this.needRegister = needRegister; + } + public boolean getNeedRegister() { + return this.needRegister; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmDevicesSpec.java b/sdk/src/main/java/org/zstack/sdk/VmDevicesSpec.java new file mode 100644 index 00000000000..91c0ccfd695 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmDevicesSpec.java @@ -0,0 +1,24 @@ +package org.zstack.sdk; + +import org.zstack.sdk.tpm.TpmSpec; +import org.zstack.sdk.NvRamSpec; + +public class VmDevicesSpec { + + public TpmSpec tpm; + public void setTpm(TpmSpec tpm) { + this.tpm = tpm; + } + public TpmSpec getTpm() { + return this.tpm; + } + + public NvRamSpec nvRam; + public void setNvRam(NvRamSpec nvRam) { + this.nvRam = nvRam; + } + public NvRamSpec getNvRam() { + return this.nvRam; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/CertificateInfo.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/CertificateInfo.java new file mode 100644 index 00000000000..c2ce6d4f302 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/CertificateInfo.java @@ -0,0 +1,55 @@ +package org.zstack.sdk.keyprovider; + + + +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/keyprovider/KeyProviderInventory.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/KeyProviderInventory.java new file mode 100644 index 00000000000..4dfb4ef08cc --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/KeyProviderInventory.java @@ -0,0 +1,63 @@ +package org.zstack.sdk.keyprovider; + + + +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/keyprovider/KmsIdentityInventory.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/KmsIdentityInventory.java new file mode 100644 index 00000000000..17a7a7f23e7 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/KmsIdentityInventory.java @@ -0,0 +1,71 @@ +package org.zstack.sdk.keyprovider; + + + +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/keyprovider/KmsInventory.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/KmsInventory.java new file mode 100644 index 00000000000..dfb4ea381cf --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/KmsInventory.java @@ -0,0 +1,80 @@ +package org.zstack.sdk.keyprovider; + +import org.zstack.sdk.keyprovider.CertificateInfo; +import org.zstack.sdk.keyprovider.KmsIdentityInventory; + +public class KmsInventory extends org.zstack.sdk.keyprovider.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/keyprovider/NkpInventory.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/NkpInventory.java new file mode 100644 index 00000000000..abf90db6a34 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/NkpInventory.java @@ -0,0 +1,39 @@ +package org.zstack.sdk.keyprovider; + + + +public class NkpInventory extends org.zstack.sdk.keyprovider.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/keyprovider/NkpRestoreInfo.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/NkpRestoreInfo.java new file mode 100644 index 00000000000..41fd0b23574 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/NkpRestoreInfo.java @@ -0,0 +1,63 @@ +package org.zstack.sdk.keyprovider; + + + +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/keyprovider/api/QueryKeyProviderAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderAction.java new file mode 100644 index 00000000000..b47c9b694eb --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..0d476612170 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..b70b28d4935 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..35e36818033 --- /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.keyprovider.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..fd6bc7c013d --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..3cbac019b87 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..9dd9e8adcfe --- /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.keyprovider.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..5b66b9f01cb --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..54423cdf317 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..018c647bf46 --- /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.keyprovider.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..5633b9e053c --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..3a914406c2a --- /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.keyprovider.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..1e67dedad41 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..1351a562ad2 --- /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.keyprovider.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..9a8cb2f46df --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..3ee11133fd4 --- /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.keyprovider.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..26303212c65 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..8c61e1f3d37 --- /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.keyprovider.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..2141553cd8f --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..36e7ef1906a --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..9af8dd3a4a0 --- /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.keyprovider.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..643edf52008 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..9e8fc6ada51 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..57773e9d145 --- /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.keyprovider.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..94e6cd6ff63 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..5e92616c46f --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..7d86f2e9ca1 --- /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.keyprovider.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..cf81539ecd6 --- /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, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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..5529454d199 --- /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.keyprovider.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/AddTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/AddTpmAction.java new file mode 100644 index 00000000000..c1510ed7c51 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/AddTpmAction.java @@ -0,0 +1,110 @@ +package org.zstack.sdk.tpm; + +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.AddTpmResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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.AddTpmResult value = res.getResult(org.zstack.sdk.tpm.AddTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.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/AddTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/AddTpmResult.java new file mode 100644 index 00000000000..3e8cd9f8bf5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/AddTpmResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm; + +import org.zstack.sdk.tpm.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/GetTpmCapabilityAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/GetTpmCapabilityAction.java new file mode 100644 index 00000000000..8b23106f2bd --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/GetTpmCapabilityAction.java @@ -0,0 +1,98 @@ +package org.zstack.sdk.tpm; + +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.GetTpmCapabilityResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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.GetTpmCapabilityResult value = res.getResult(org.zstack.sdk.tpm.GetTpmCapabilityResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.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/GetTpmCapabilityResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/GetTpmCapabilityResult.java new file mode 100644 index 00000000000..bc7ea946fe2 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/GetTpmCapabilityResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm; + +import org.zstack.sdk.tpm.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/QueryTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/QueryTpmAction.java new file mode 100644 index 00000000000..e731b285307 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/QueryTpmAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.tpm; + +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.QueryTpmResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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.QueryTpmResult value = res.getResult(org.zstack.sdk.tpm.QueryTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.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/QueryTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/QueryTpmResult.java new file mode 100644 index 00000000000..32b2e71e779 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/QueryTpmResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.tpm; + + + +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/RemoveTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/RemoveTpmAction.java new file mode 100644 index 00000000000..96739990880 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/RemoveTpmAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk.tpm; + +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.RemoveTpmResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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.RemoveTpmResult value = res.getResult(org.zstack.sdk.tpm.RemoveTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.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/RemoveTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/RemoveTpmResult.java new file mode 100644 index 00000000000..a1011f5c254 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/RemoveTpmResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk.tpm; + + + +public class RemoveTpmResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/TpmCapabilityView.java b/sdk/src/main/java/org/zstack/sdk/tpm/TpmCapabilityView.java new file mode 100644 index 00000000000..7f80f7e737e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/TpmCapabilityView.java @@ -0,0 +1,79 @@ +package org.zstack.sdk.tpm; + + + +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/TpmInventory.java b/sdk/src/main/java/org/zstack/sdk/tpm/TpmInventory.java new file mode 100644 index 00000000000..b3ab5808b5d --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/TpmInventory.java @@ -0,0 +1,47 @@ +package org.zstack.sdk.tpm; + + + +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/tpm/TpmSpec.java b/sdk/src/main/java/org/zstack/sdk/tpm/TpmSpec.java new file mode 100644 index 00000000000..7b345652c7c --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/TpmSpec.java @@ -0,0 +1,31 @@ +package org.zstack.sdk.tpm; + + + +public class TpmSpec { + + public boolean enable; + public void setEnable(boolean enable) { + this.enable = enable; + } + public boolean getEnable() { + return this.enable; + } + + public java.lang.String tpmUuid; + public void setTpmUuid(java.lang.String tpmUuid) { + this.tpmUuid = tpmUuid; + } + public java.lang.String getTpmUuid() { + return this.tpmUuid; + } + + public java.lang.String keyProviderUuid; + public void setKeyProviderUuid(java.lang.String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + public java.lang.String getKeyProviderUuid() { + return this.keyProviderUuid; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/UpdateTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/UpdateTpmAction.java new file mode 100644 index 00000000000..b19117dda2f --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/UpdateTpmAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk.tpm; + +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.UpdateTpmResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + 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.UpdateTpmResult value = res.getResult(org.zstack.sdk.tpm.UpdateTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.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/UpdateTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/UpdateTpmResult.java new file mode 100644 index 00000000000..140fcccedd8 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/UpdateTpmResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm; + +import org.zstack.sdk.tpm.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/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/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index a09908b89ca..204bcd7b89f 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -3689,6 +3689,33 @@ abstract class ApiHelper { } + def attachDGpuToVm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDGpuToVmAction.class) Closure c) { + def a = new org.zstack.sdk.AttachDGpuToVmAction() + 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 attachDataVolumeToHost(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDataVolumeToHostAction.class) Closure c) { def a = new org.zstack.sdk.AttachDataVolumeToHostAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -18620,33 +18647,6 @@ abstract class ApiHelper { } - def attachDGpuToVm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDGpuToVmAction.class) Closure c) { - def a = new org.zstack.sdk.AttachDGpuToVmAction() - 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 detachBaremetalPxeServerFromCluster(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DetachBaremetalPxeServerFromClusterAction.class) Closure c) { def a = new org.zstack.sdk.DetachBaremetalPxeServerFromClusterAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -40683,33 +40683,6 @@ abstract class ApiHelper { } - def updateVmDGpu(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmDGpuAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateVmDGpuAction() - 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 setVmEmulatorPinning(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SetVmEmulatorPinningAction.class) Closure c) { def a = new org.zstack.sdk.SetVmEmulatorPinningAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -48108,6 +48081,33 @@ abstract class ApiHelper { } + def updateVmDGpu(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmDGpuAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateVmDGpuAction() + 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 updateVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstanceAction.class) Closure c) { def a = new org.zstack.sdk.UpdateVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -52329,13 +52329,15 @@ abstract class ApiHelper { } - def degradeFromLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.DegradeFromLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.DegradeFromLicenseServerAction() + 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) { @@ -52356,8 +52358,8 @@ abstract class ApiHelper { } - def getLicenseAuthorizedCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.GetLicenseAuthorizedCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.GetLicenseAuthorizedCapacityAction() + 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 @@ -52383,8 +52385,8 @@ abstract class ApiHelper { } - def getLicenseNodeUsageDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.GetLicenseNodeUsageDetailsAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.GetLicenseNodeUsageDetailsAction() + 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 @@ -52410,8 +52412,8 @@ abstract class ApiHelper { } - def isLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.IsLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.IsLicenseServerAction() + 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 @@ -52437,15 +52439,13 @@ abstract class ApiHelper { } - def queryLicenseAuthorizedNode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.QueryLicenseAuthorizedNodeAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.QueryLicenseAuthorizedNodeAction() + 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -52466,13 +52466,15 @@ abstract class ApiHelper { } - def registerLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.RegisterLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.RegisterLicenseServerAction() + 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) { @@ -52493,8 +52495,8 @@ abstract class ApiHelper { } - def requestLicenseCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.RequestLicenseCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.RequestLicenseCapacityAction() + 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 @@ -52520,8 +52522,8 @@ abstract class ApiHelper { } - def syncLicenseCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.SyncLicenseCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.SyncLicenseCapacityAction() + 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 @@ -52547,8 +52549,8 @@ abstract class ApiHelper { } - def unregisterLicenseRequestedApplication(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UnregisterLicenseRequestedApplicationAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.UnregisterLicenseRequestedApplicationAction() + 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 @@ -52574,8 +52576,8 @@ abstract class ApiHelper { } - def unregisterLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UnregisterLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.UnregisterLicenseServerAction() + 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 @@ -52601,8 +52603,8 @@ abstract class ApiHelper { } - def upgradeToLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UpgradeToLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.UpgradeToLicenseServerAction() + 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 @@ -52628,8 +52630,8 @@ abstract class ApiHelper { } - def verifyLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.VerifyLicenseServerAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.VerifyLicenseServerAction() + 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 @@ -52655,8 +52657,8 @@ abstract class ApiHelper { } - def withdrawLicenseCapacityApplication(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.WithdrawLicenseCapacityApplicationAction.class) Closure c) { - def a = new org.zstack.sdk.license.api.server.WithdrawLicenseCapacityApplicationAction() + 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 @@ -52682,8 +52684,8 @@ abstract class ApiHelper { } - def createL2GeneveNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.CreateL2GeneveNetworkAction.class) Closure c) { - def a = new org.zstack.sdk.network.zns.CreateL2GeneveNetworkAction() + 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 @@ -52709,8 +52711,8 @@ abstract class ApiHelper { } - def getCandidateZnsNicModesForAttachingL3Network(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.GetCandidateZnsNicModesForAttachingL3NetworkAction.class) Closure c) { - def a = new org.zstack.sdk.network.zns.GetCandidateZnsNicModesForAttachingL3NetworkAction() + 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 @@ -52736,8 +52738,8 @@ abstract class ApiHelper { } - def queryZnsTenant(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsTenantAction.class) Closure c) { - def a = new org.zstack.sdk.network.zns.QueryZnsTenantAction() + 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 @@ -52765,15 +52767,13 @@ abstract class ApiHelper { } - def queryZnsTenantRouter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsTenantRouterAction.class) Closure c) { - def a = new org.zstack.sdk.network.zns.QueryZnsTenantRouterAction() + 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -52794,8 +52794,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 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 @@ -52821,8 +52821,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 degradeFromLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.DegradeFromLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.DegradeFromLicenseServerAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -52848,8 +52848,8 @@ 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 getLicenseAuthorizedCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.GetLicenseAuthorizedCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.GetLicenseAuthorizedCapacityAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -52875,8 +52875,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 getLicenseNodeUsageDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.GetLicenseNodeUsageDetailsAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.GetLicenseNodeUsageDetailsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -52902,8 +52902,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 isLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.IsLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.IsLicenseServerAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -52929,13 +52929,15 @@ 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 queryLicenseAuthorizedNode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.QueryLicenseAuthorizedNodeAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.QueryLicenseAuthorizedNodeAction() 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) { @@ -52956,8 +52958,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 registerLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.RegisterLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.RegisterLicenseServerAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -52983,8 +52985,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 requestLicenseCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.RequestLicenseCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.RequestLicenseCapacityAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -53010,15 +53012,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 syncLicenseCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.SyncLicenseCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.SyncLicenseCapacityAction() 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) { @@ -53039,15 +53039,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 unregisterLicenseRequestedApplication(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UnregisterLicenseRequestedApplicationAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.UnregisterLicenseRequestedApplicationAction() 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) { @@ -53068,15 +53066,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 unregisterLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UnregisterLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.UnregisterLicenseServerAction() 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) { @@ -53097,15 +53093,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 upgradeToLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.UpgradeToLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.UpgradeToLicenseServerAction() 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) { @@ -53126,15 +53120,40 @@ 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 verifyLicenseServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.VerifyLicenseServerAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.VerifyLicenseServerAction() 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 withdrawLicenseCapacityApplication(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.license.api.server.WithdrawLicenseCapacityApplicationAction.class) Closure c) { + def a = new org.zstack.sdk.license.api.server.WithdrawLicenseCapacityApplicationAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53155,8 +53174,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 createL2GeneveNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.CreateL2GeneveNetworkAction.class) Closure c) { + def a = new org.zstack.sdk.network.zns.CreateL2GeneveNetworkAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -53182,8 +53201,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 getCandidateZnsNicModesForAttachingL3Network(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.GetCandidateZnsNicModesForAttachingL3NetworkAction.class) Closure c) { + def a = new org.zstack.sdk.network.zns.GetCandidateZnsNicModesForAttachingL3NetworkAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -53209,13 +53228,15 @@ 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 queryZnsTenant(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsTenantAction.class) Closure c) { + def a = new org.zstack.sdk.network.zns.QueryZnsTenantAction() 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) { @@ -53236,13 +53257,15 @@ 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 queryZnsTenantRouter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.network.zns.QueryZnsTenantRouterAction.class) Closure c) { + def a = new org.zstack.sdk.network.zns.QueryZnsTenantRouterAction() 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) { @@ -53263,8 +53286,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 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 @@ -53290,8 +53313,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 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 @@ -53317,8 +53340,8 @@ abstract class ApiHelper { } - def createSNSAliyunSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.aliyunsms.CreateSNSAliyunSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.aliyunsms.CreateSNSAliyunSmsEndpointAction() + 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 @@ -53344,8 +53367,8 @@ abstract class ApiHelper { } - def validateSNSAliyunSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.aliyunsms.ValidateSNSAliyunSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.aliyunsms.ValidateSNSAliyunSmsEndpointAction() + 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 @@ -53371,8 +53394,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 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 @@ -53398,8 +53421,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 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 @@ -53425,15 +53448,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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53454,15 +53475,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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53483,41 +53502,16 @@ 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 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() - - 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()) - } - } + a.conditions = a.conditions.collect { it.toString() } - 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 - c() - - if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid @@ -53537,13 +53531,15 @@ 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 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) { @@ -53564,13 +53560,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 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) { @@ -53591,13 +53589,15 @@ abstract class ApiHelper { } - 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() + 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 c() + a.conditions = a.conditions.collect { it.toString() } + if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53618,13 +53618,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 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 c() + a.conditions = a.conditions.collect { it.toString() } + if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53645,8 +53647,8 @@ abstract class ApiHelper { } - 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() + 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 @@ -53672,8 +53674,8 @@ 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 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 @@ -53699,15 +53701,13 @@ 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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53728,15 +53728,13 @@ 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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53757,15 +53755,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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -53786,8 +53782,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 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 @@ -53813,8 +53809,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 createSNSAliyunSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.aliyunsms.CreateSNSAliyunSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.aliyunsms.CreateSNSAliyunSmsEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -53840,8 +53836,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 validateSNSAliyunSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.aliyunsms.ValidateSNSAliyunSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.aliyunsms.ValidateSNSAliyunSmsEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -53867,8 +53863,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 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 @@ -53894,8 +53890,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 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 @@ -53921,8 +53917,8 @@ 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 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 @@ -53950,8 +53946,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 @@ -53979,35 +53975,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() - 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 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 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 @@ -54033,8 +54002,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 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 @@ -54060,8 +54029,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 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 @@ -54087,8 +54056,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 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 @@ -54114,15 +54083,13 @@ 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 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() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54143,8 +54110,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 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 @@ -54170,8 +54137,8 @@ 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 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 @@ -54197,8 +54164,8 @@ 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 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 @@ -54224,8 +54191,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 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 @@ -54253,40 +54220,15 @@ 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 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) { - 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 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 - c() - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54307,13 +54249,15 @@ abstract class ApiHelper { } - def createSNSPluginEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.plugin.CreateSNSPluginEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.plugin.CreateSNSPluginEndpointAction() + 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 c() + a.conditions = a.conditions.collect { it.toString() } + if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54334,15 +54278,13 @@ abstract class ApiHelper { } - def querySNSPluginEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.plugin.QuerySNSPluginEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.plugin.QuerySNSPluginEndpointAction() + 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54363,8 +54305,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 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 @@ -54390,8 +54332,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 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 @@ -54417,15 +54359,13 @@ 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 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54446,8 +54386,8 @@ 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 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 @@ -54473,40 +54413,15 @@ 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 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 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 createSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.CreateSNSUniversalSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.universalsms.CreateSNSUniversalSmsEndpointAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54527,8 +54442,8 @@ abstract class ApiHelper { } - def querySNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.QuerySNSUniversalSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.universalsms.QuerySNSUniversalSmsEndpointAction() + 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 @@ -54556,8 +54471,8 @@ abstract class ApiHelper { } - def updateSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.UpdateSNSUniversalSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.universalsms.UpdateSNSUniversalSmsEndpointAction() + 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 @@ -54583,8 +54498,8 @@ abstract class ApiHelper { } - def validateSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.ValidateSNSUniversalSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.universalsms.ValidateSNSUniversalSmsEndpointAction() + 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 @@ -54610,8 +54525,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 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 @@ -54637,8 +54552,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 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 @@ -54664,15 +54579,13 @@ 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 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() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54693,8 +54606,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 @@ -54722,116 +54635,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() - 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 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 addTicketTypesToTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.AddTicketTypesToTicketFlowCollectionAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.AddTicketTypesToTicketFlowCollectionAction() + 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 @@ -54857,8 +54662,8 @@ abstract class ApiHelper { } - def changeTicketFlowCollectionState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.ChangeTicketFlowCollectionStateAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.ChangeTicketFlowCollectionStateAction() + 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 @@ -54884,8 +54689,8 @@ abstract class ApiHelper { } - def changeTicketStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.ChangeTicketStatusAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.ChangeTicketStatusAction() + 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 @@ -54911,40 +54716,15 @@ abstract class ApiHelper { } - def createTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.CreateTicketAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.CreateTicketAction() + 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) { - 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 deleteTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.DeleteTicketAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.DeleteTicketAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -54965,8 +54745,8 @@ abstract class ApiHelper { } - def deleteTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.DeleteTicketFlowCollectionAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.DeleteTicketFlowCollectionAction() + 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 @@ -54992,15 +54772,13 @@ abstract class ApiHelper { } - def queryArchiveTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryArchiveTicketAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.QueryArchiveTicketAction() + 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -55021,15 +54799,13 @@ abstract class ApiHelper { } - def queryArchiveTicketHistory(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryArchiveTicketHistoryAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.QueryArchiveTicketHistoryAction() + def createSNSPluginEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.plugin.CreateSNSPluginEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.plugin.CreateSNSPluginEndpointAction() 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) { @@ -55050,8 +54826,8 @@ abstract class ApiHelper { } - def queryTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.QueryTicketAction() + def querySNSPluginEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.plugin.QuerySNSPluginEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.plugin.QuerySNSPluginEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -55079,15 +54855,13 @@ abstract class ApiHelper { } - def queryTicketFlow(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketFlowAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.QueryTicketFlowAction() + 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 c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -55108,15 +54882,733 @@ abstract class ApiHelper { } - def queryTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketFlowCollectionAction.class) Closure c) { - def a = new org.zstack.sdk.ticket.api.QueryTicketFlowCollectionAction() + 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 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 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 + 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 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 createSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.CreateSNSUniversalSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.universalsms.CreateSNSUniversalSmsEndpointAction() + 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 querySNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.QuerySNSUniversalSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.universalsms.QuerySNSUniversalSmsEndpointAction() + 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 updateSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.UpdateSNSUniversalSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.universalsms.UpdateSNSUniversalSmsEndpointAction() + 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 validateSNSUniversalSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.universalsms.ValidateSNSUniversalSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.universalsms.ValidateSNSUniversalSmsEndpointAction() + 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 + 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 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 + 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 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 addTicketTypesToTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.AddTicketTypesToTicketFlowCollectionAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.AddTicketTypesToTicketFlowCollectionAction() + 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 changeTicketFlowCollectionState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.ChangeTicketFlowCollectionStateAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.ChangeTicketFlowCollectionStateAction() + 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 changeTicketStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.ChangeTicketStatusAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.ChangeTicketStatusAction() + 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 createTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.CreateTicketAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.CreateTicketAction() + 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 deleteTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.DeleteTicketAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.DeleteTicketAction() + 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 deleteTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.DeleteTicketFlowCollectionAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.DeleteTicketFlowCollectionAction() + 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 queryArchiveTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryArchiveTicketAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.QueryArchiveTicketAction() + 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 queryArchiveTicketHistory(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryArchiveTicketHistoryAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.QueryArchiveTicketHistoryAction() + 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 queryTicket(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.QueryTicketAction() + 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 queryTicketFlow(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketFlowAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.QueryTicketFlowAction() + 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 queryTicketFlowCollection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ticket.api.QueryTicketFlowCollectionAction.class) Closure c) { + def a = new org.zstack.sdk.ticket.api.QueryTicketFlowCollectionAction() + 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) { @@ -55384,6 +55876,143 @@ abstract class ApiHelper { } + def addTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.AddTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.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.GetTpmCapabilityAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.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.QueryTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.QueryTpmAction() + 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 removeTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.RemoveTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.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.UpdateTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.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()) + } + } + + def ackAlarmData(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zwatch.alarm.AckAlarmDataAction.class) Closure c) { def a = new org.zstack.sdk.zwatch.alarm.AckAlarmDataAction() a.sessionId = Test.currentEnvSpec?.session?.uuid diff --git a/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy b/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy index fec7d2c2eaf..0254acb65ea 100755 --- a/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy @@ -562,12 +562,7 @@ class EnvSpec extends ApiHelper implements Node { completion.done() } } - }).run(new WhileDoneCompletion(null) { - @Override - void done(org.zstack.header.errorcode.ErrorCodeList errorCodeList) { - latch.countDown() - } - }) + }).run(WhileDoneCompletions.countDownLatch(latch)) def ret = latch.await(1, TimeUnit.MINUTES) if (!ret) { diff --git a/testlib/src/main/java/org/zstack/testlib/WhileDoneCompletions.java b/testlib/src/main/java/org/zstack/testlib/WhileDoneCompletions.java new file mode 100644 index 00000000000..5981498c939 --- /dev/null +++ b/testlib/src/main/java/org/zstack/testlib/WhileDoneCompletions.java @@ -0,0 +1,20 @@ +package org.zstack.testlib; + +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCodeList; + +import java.util.concurrent.CountDownLatch; + +final class WhileDoneCompletions { + private WhileDoneCompletions() { + } + + static WhileDoneCompletion countDownLatch(CountDownLatch latch) { + return new WhileDoneCompletion(null) { + @Override + public void done(ErrorCodeList errorCodeList) { + latch.countDown(); + } + }; + } +}