From d7b3c123f9a11878abc6f9c09a5d8b02d4594284 Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Thu, 14 May 2026 18:21:40 +0800 Subject: [PATCH] [vm]: avoid StopVmGC stopping live vm Only allow StopVmGC to send StopVmOnHypervisorMsg while the VM is still Stopped. If the VM has already been started again before the GC runs, cancel the GC job instead. Resolves: ZSTAC-84174 Change-Id: Ib58111c4237bd41abc7c9b0d1b4e05f9bee24ec4 --- .../java/org/zstack/compute/vm/StopVmGC.java | 6 +++ .../test/integration/kvm/vm/VmGCCase.groovy | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/compute/src/main/java/org/zstack/compute/vm/StopVmGC.java b/compute/src/main/java/org/zstack/compute/vm/StopVmGC.java index a55de8878cf..5f4cb98f800 100755 --- a/compute/src/main/java/org/zstack/compute/vm/StopVmGC.java +++ b/compute/src/main/java/org/zstack/compute/vm/StopVmGC.java @@ -43,6 +43,12 @@ protected void triggerNow(GCCompletion completion) { completion.cancel(); return; } + if (state != VmInstanceState.Stopped) { + logger.info(String.format("cancel StopVmGC for vm[uuid:%s,name:%s] on host[uuid:%s], current state is %s, only stopped vm can be stopped on hypervisor by this GC", + inventory.getUuid(), inventory.getName(), hostUuid, state)); + completion.cancel(); + return; + } FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("gc-stop-vm-%s-on-host-%s", inventory.getUuid(), hostUuid)); diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmGCCase.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmGCCase.groovy index ed2b5520d31..f9925859d5c 100755 --- a/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmGCCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmGCCase.groovy @@ -225,6 +225,7 @@ class VmGCCase extends SubCase { testStopVmWhenHostDisconnect() testStopVmGCJobCancelAfterVmDeleted() + testStopVmGCJobCancelAfterVmStarted() testStopVmGCJobCancelAfterHostDeleted() } } @@ -317,6 +318,46 @@ class VmGCCase extends SubCase { } } + void testStopVmGCJobCancelAfterVmStarted() { + VmInstanceInventory vm = createGCCandidateStoppedVm() + + vm = startVmInstance { + uuid = vm.uuid + } as VmInstanceInventory + + assert dbFindByUuid(vm.uuid, VmInstanceVO.class).state == VmInstanceState.Running + + KVMAgentCommands.StopVmCmd cmd = null + env.afterSimulator(KVMConstant.KVM_STOP_VM_PATH) { rsp, HttpEntity e -> + cmd = json(e.body, KVMAgentCommands.StopVmCmd.class) + return rsp + } + + // reconnect host to trigger the GC + reconnectHost { + uuid = vm.hostUuid + } + + GarbageCollectorInventory inv = null + retryInSecs { + inv = queryGCJob { + conditions=["context~=%${vm.uuid}%"] + }[0] + + // the GC job is cancelled because the vm has already been started again + assert cmd == null + assert inv.status == GCStatus.Done.toString() + } + + deleteGCJob { + uuid = inv.uuid + } + + destroyVmInstance { + uuid = vm.uuid + } + } + void testStopVmWhenHostDisconnect() { VmInstanceInventory vm = createGCCandidateStoppedVm()