From 14b3e0faaa80a2eea36e05fc7d2796c85d4a5efe Mon Sep 17 00:00:00 2001 From: Zhang Wenhao Date: Sat, 11 Apr 2026 17:45:18 +0800 Subject: [PATCH] [kvm]: mark TPM VM host files changed on start/shutdown When a VM with TPM starts or shuts down, NvRam/TpmState data must have changed. Preemptively set changeDate on the corresponding VmHostFileVO so the periodic tracker knows to sync them, even if the direct sync fails. Also add ResourceDestinationMaker check to all VM canonical event handlers in KvmSecureBootManager to ensure only the owning management node processes each event. Resolves: ZSV-11779 Related: ZSV-11310 Change-Id: I6e6d6c6c647175716669756b75756a6f72657277 --- .../compute/vm/devices/VmTpmManager.java | 30 ++++++- .../kvm/efi/KvmSecureBootExtensions.java | 45 ++++------ .../zstack/kvm/efi/KvmSecureBootManager.java | 90 ++++++++++++++++--- .../zstack/kvm/efi/KvmVmHostFileFactory.java | 19 ++++ 4 files changed, 139 insertions(+), 45 deletions(-) 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 index 757696c6c1d..b486fe09379 100644 --- a/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java @@ -1,20 +1,28 @@ package org.zstack.compute.vm.devices; import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmSystemTags; import org.zstack.core.Platform; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.header.image.ImageBootMode; import org.zstack.header.tpm.entity.TpmVO; import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.additions.VmHostFileType; import org.zstack.resourceconfig.ResourceConfig; import org.zstack.resourceconfig.ResourceConfigFacade; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; +import static org.zstack.header.vm.additions.VmHostFileType.NvRam; +import static org.zstack.header.vm.additions.VmHostFileType.TpmState; +import static org.zstack.utils.CollectionDSL.list; public class VmTpmManager { private static final CLogger logger = Utils.getLogger(VmTpmManager.class); @@ -58,13 +66,27 @@ public static boolean isUefiBootMode(String bootMode) { } public boolean needRegisterNvRam(String vmUuid) { - boolean tpmExists = Q.New(TpmVO.class) + 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 (tpmExists) { - return true; + if (hasTpm) { + return new HashSet<>(list(NvRam, TpmState)); } ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(ENABLE_UEFI_SECURE_BOOT.getIdentity()); - return resourceConfig.getResourceConfigValue(vmUuid, Boolean.class); + 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/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java index 35ff290762a..a30a6691f93 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java @@ -3,7 +3,6 @@ import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.compute.vm.VmGlobalConfig; -import org.zstack.compute.vm.VmSystemTags; import org.zstack.compute.vm.devices.VmTpmManager; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; @@ -23,8 +22,6 @@ import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.message.MessageReply; -import org.zstack.header.tpm.entity.TpmVO; -import org.zstack.header.tpm.entity.TpmVO_; import org.zstack.header.vm.DiskAO; import org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint; import org.zstack.header.vm.VmInstanceVO; @@ -79,7 +76,6 @@ import java.util.Map; import java.util.Objects; -import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; import static org.zstack.core.Platform.operr; import static org.zstack.header.vm.additions.VmHostFileSyncReason.PostMigration; import static org.zstack.header.vm.additions.VmHostFileSyncReason.BeforeHaStart; @@ -87,6 +83,7 @@ import static org.zstack.header.vm.additions.VmHostFileSyncReason.PrepareRead; import static org.zstack.header.vm.additions.VmHostFileSyncReason.ResourceRelease; import static org.zstack.header.vm.additions.VmHostFileSyncReason.SnapshotGroupOnlineBackup; +import static org.zstack.header.vm.additions.VmHostFileType.NvRam; import static org.zstack.kvm.KVMConstant.*; import static org.zstack.utils.CollectionDSL.list; @@ -107,6 +104,8 @@ public class KvmSecureBootExtensions implements KVMStartVmExtensionPoint, private ResourceConfigFacade resourceConfigFacade; @Autowired private DatabaseFacade databaseFacade; + @Autowired + private KvmVmHostFileFactory vmHostFileFactory; private final Object hostFileLock = new Object(); @@ -141,7 +140,7 @@ private void prepareNvRamToStartVmCmd(KVMAgentCommands.StartVmCmd cmd, NvRamSpec final Timestamp now = Timestamp.from(Instant.now()); VmHostFileVO nvRamFile = Q.New(VmHostFileVO.class) .eq(VmHostFileVO_.vmInstanceUuid, cmd.getVmInstanceUuid()) - .eq(VmHostFileVO_.type, VmHostFileType.NvRam) + .eq(VmHostFileVO_.type, NvRam) .eq(VmHostFileVO_.hostUuid, host.getUuid()) .find(); if (nvRamFile == null) { @@ -149,7 +148,7 @@ private void prepareNvRamToStartVmCmd(KVMAgentCommands.StartVmCmd cmd, NvRamSpec nvRamFile.setUuid(Platform.getUuid()); nvRamFile.setHostUuid(host.getUuid()); nvRamFile.setVmInstanceUuid(cmd.getVmInstanceUuid()); - nvRamFile.setType(VmHostFileType.NvRam); + nvRamFile.setType(NvRam); nvRamFile.setPath(volume.getInstallPath()); nvRamFile.setCreateDate(now); nvRamFile.setResourceName("NvRam file for " + cmd.getVmInstanceUuid()); @@ -180,29 +179,17 @@ private void prepareNvRamBeforeMigration(VmInstanceInventory vm, String dstHostU return; } - String tpmUuid = Q.New(TpmVO.class) - .eq(TpmVO_.vmInstanceUuid, vm.getUuid()) - .select(TpmVO_.uuid) - .findValue(); - boolean needRegisterNvRam = tpmUuid != null; + boolean needRegisterNvRam = vmHostFileFactory.needRegister(NvRam, vm.getUuid()); if (!needRegisterNvRam) { - String bootMode = VmSystemTags.BOOT_MODE.getTokenByResourceUuid(vm.getUuid(), VmSystemTags.BOOT_MODE_TOKEN); - if (isUefiBootMode(bootMode)) { - ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(ENABLE_UEFI_SECURE_BOOT.getIdentity()); - needRegisterNvRam = resourceConfig.getResourceConfigValue(vm.getUuid(), Boolean.class) == Boolean.TRUE; - } - - if (!needRegisterNvRam) { - completion.success(); - return; - } + completion.success(); + return; } SimpleFlowChain.of("prepare-nvram-before-vm-" + vm.getUuid() + "-migrate") .then("prepare-nvram-folder-on-dest-host", trigger -> { VmHostFileTO to = new VmHostFileTO(); to.setPath(buildNvramFilePath(vm.getUuid())); - to.setType(VmHostFileType.NvRam.toString()); + to.setType(NvRam.toString()); to.setOperation(VmHostFileOperation.Prepare.toString()); RewriteVmHostFilesContext context = new RewriteVmHostFilesContext(); @@ -290,7 +277,7 @@ public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) PrepareHostFileContext context = new PrepareHostFileContext(); context.hostUuid = spec.getDestHost().getUuid(); context.vmUuid = spec.getVmInventory().getUuid(); - context.type = VmHostFileType.NvRam; + context.type = NvRam; context.backupUuid = nvRamSpec.getBackupFileUuid(); context.syncReason = "pre-instantiate VM resource"; prepareHostFileOnHost(context, completion); @@ -349,7 +336,7 @@ public void run(FlowTrigger trigger, Map data) { syncMsg.setVmUuid(context.vmUuid); syncMsg.setSyncReason(PrepareRead.reason(context.syncReason)); - if (vmHostFile.getType() == VmHostFileType.NvRam) { + if (vmHostFile.getType() == NvRam) { context.path = vmHostFile.getPath(); syncMsg.setNvRamPath(context.path); } else if (vmHostFile.getType() == VmHostFileType.TpmState) { @@ -471,7 +458,7 @@ public void run(FlowTrigger trigger, Map data) { syncMsg.setVmUuid(context.vmUuid); syncMsg.setSyncReason(PrepareReRead.reason(context.syncReason)); - if (context.type == VmHostFileType.NvRam) { + if (context.type == NvRam) { syncMsg.setNvRamPath(context.path); } else if (context.type == VmHostFileType.TpmState) { syncMsg.setTpmStateFolder(context.path); @@ -574,7 +561,7 @@ public void releaseVmResource(VmInstanceSpec spec, Completion completion) { syncMsg.setSyncReason(ResourceRelease.reason()); for (VmHostFileVO file : vmHostFiles) { - if (file.getType() == VmHostFileType.NvRam) { + if (file.getType() == NvRam) { syncMsg.setNvRamPath(file.getPath()); } else if (file.getType() == VmHostFileType.TpmState) { syncMsg.setTpmStateFolder(file.getPath()); @@ -633,7 +620,7 @@ public void beforeHaStartVmInstance(String vmUuid, String judgerClassName, List< syncMsg.setSyncReason(BeforeHaStart.reason()); for (VmHostFileVO file : vmHostFiles) { - if (file.getType() == VmHostFileType.NvRam) { + if (file.getType() == NvRam) { syncMsg.setNvRamPath(file.getPath()); } else if (file.getType() == VmHostFileType.TpmState) { syncMsg.setTpmStateFolder(file.getPath()); @@ -699,7 +686,7 @@ public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid, NoErrorC syncMsg.setSyncReason(PostMigration.reason()); for (VmHostFileVO file : vmHostFiles) { - if (file.getType() == VmHostFileType.NvRam) { + if (file.getType() == NvRam) { syncMsg.setNvRamPath(file.getPath()); } else if (file.getType() == VmHostFileType.TpmState) { syncMsg.setTpmStateFolder(file.getPath()); @@ -759,7 +746,7 @@ public void afterVolumeLiveSnapshotGroupCreatedOnBackend(CreateVolumesSnapshotOv syncMsg.setHostUuid(hostUuid); for (VmHostFileVO file : hostFiles) { - if (file.getType() == VmHostFileType.NvRam) { + if (file.getType() == NvRam) { syncMsg.setNvRamPath(buildNvramSnapshotBackupFilePath(vmUuid)); } else if (file.getType() == VmHostFileType.TpmState) { syncMsg.setTpmStateFolder(buildTpmStateSnapshotBackupFilePath(vmUuid)); 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 index a9a8b152b2c..5a62cc6dc6d 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java @@ -8,6 +8,7 @@ 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; @@ -27,8 +28,6 @@ import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; -import org.zstack.header.tpm.entity.TpmVO; -import org.zstack.header.tpm.entity.TpmVO_; import org.zstack.header.vm.VmCanonicalEvents; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceVO; @@ -76,7 +75,6 @@ import java.util.Set; import java.util.function.Function; -import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; 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; @@ -104,6 +102,8 @@ public class KvmSecureBootManager extends AbstractService { private KvmVmHostFileFactory vmHostFileFactory; @Autowired private TimeHelper timeHelper; + @Autowired + private ResourceDestinationMaker resourceDestinationMaker; @Override public boolean start() { @@ -118,10 +118,27 @@ public boolean stop() { @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) @@ -134,6 +151,7 @@ protected void run(Map tokens, Object data) { if (hostUuid == null) { hostUuid = (String) tuple.get(1); } + markVmHostFilesChanged(vmUuid, hostUuid); List hostFiles = Q.New(VmHostFileVO.class) .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) @@ -173,6 +191,55 @@ public void run(MessageReply reply) { }); } + /** + * 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); @@ -473,22 +540,21 @@ protected void scripts() { private void handle(CloneVmHostFileMsg msg) { CloneVmHostFileReply reply = new CloneVmHostFileReply(); - boolean hasTpm = Q.New(TpmVO.class) - .eq(TpmVO_.vmInstanceUuid, msg.getSrcVmUuid()) - .isExists(); - ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(ENABLE_UEFI_SECURE_BOOT.getIdentity()); - boolean secureBoot = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class); - if (!hasTpm && !secureBoot) { + final Set types = vmHostFileFactory.vmHostFileTypeNeedRegisterForVm(msg.getSrcVmUuid()); + if (types.isEmpty()) { bus.reply(msg, reply); return; } CloneVmHostFileContext context = new CloneVmHostFileContext(); - context.typesNeedClone.add(VmHostFileType.NvRam); - if (hasTpm) { + if (types.contains(VmHostFileType.NvRam)) { + context.typesNeedClone.add(VmHostFileType.NvRam); + } + + if (types.contains(VmHostFileType.TpmState)) { boolean resetTpm; if (msg.getResetTpm() == null) { - resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity()); + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity()); resetTpm = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class); } else { resetTpm = msg.getResetTpm(); 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 index 0aaaad0cc9a..c4e13957cdf 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java @@ -1,15 +1,26 @@ package org.zstack.kvm.efi; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.devices.VmTpmManager; import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostFileType; import org.zstack.header.vm.additions.VmHostFileVO; import org.zstack.kvm.tpm.TpmStateVmHostBackupFileBase; import org.zstack.kvm.tpm.TpmStateVmHostFileBase; import org.zstack.kvm.vmfiles.AbstractVmHostBackupFileBase; import org.zstack.kvm.vmfiles.AbstractVmHostFileBase; +import org.zstack.resourceconfig.ResourceConfigFacade; + +import java.util.Set; import static org.zstack.core.Platform.operr; public class KvmVmHostFileFactory { + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private VmTpmManager vmTpmManager; + public AbstractVmHostFileBase createBase(VmHostFileVO file) { switch (file.getType()) { case NvRam: return new NvRamVmHostFileBase(file); @@ -25,4 +36,12 @@ public AbstractVmHostBackupFileBase createBackupBase(VmHostBackupFileVO backupFi default: throw operr("invalid VM host file type: " + backupFile.getType()).toException(); } } + + public Set vmHostFileTypeNeedRegisterForVm(String vmUuid) { + return vmTpmManager.vmHostFileTypeNeedRegisterForVm(vmUuid); + } + + public boolean needRegister(VmHostFileType type, String vmUuid) { + return vmTpmManager.needRegister(type, vmUuid); + } }