From c5ad6ea7c41edab93fceecbb442dd95de3df622f Mon Sep 17 00:00:00 2001 From: Jean-Louis Dupond Date: Mon, 17 Aug 2026 14:25:19 +0200 Subject: [PATCH] bll: Allow resuming Paused/Suspended VMs with older custom compatibility version When a data center is upgraded to a newer compatibility version, VMs that were hibernated (Suspended) or Paused with an older custom compatibility version could no longer be resumed. The validation in RunVmCommand rejected them with ACTION_TYPE_FAILED_VM_COMPATIBILITY_VERSION_NOT_SUPPORTED, even though these VMs were already running with that version before the upgrade and are only being resumed, not started fresh. Skip the custom compatibility version check when the VM status is Paused or Suspended, so that the RESUME_PAUSE and RESUME_HIBERNATE flows can proceed. The check is still enforced for VMs being started from Down status, preserving the existing protection against running a VM with an unsupported compatibility version. Added tests covering: - Down VM with older custom compatibility version still fails validation - Suspended VM with older custom compatibility version now passes - Paused VM with older custom compatibility version now passes Signed-off-by: Jean-Louis Dupond --- .../ovirt/engine/core/bll/RunVmCommand.java | 9 ++- .../engine/core/bll/RunVmCommandTest.java | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java index 8aacfc87008..17c3185e350 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/RunVmCommand.java @@ -1078,8 +1078,13 @@ protected boolean validateImpl() { return false; } - if (getVm().getCustomCompatibilityVersion() != null && - vm.getCustomCompatibilityVersion().less(getStoragePool().getCompatibilityVersion())) { + // Skip the compatibility version check when resuming a Paused or Suspended VM, + // since the VM was already running with its custom compatibility version before + // the data center was upgraded and is only being resumed, not started fresh. + if (vm.getCustomCompatibilityVersion() != null && + vm.getCustomCompatibilityVersion().less(getStoragePool().getCompatibilityVersion()) && + vm.getStatus() != VMStatus.Paused && + vm.getStatus() != VMStatus.Suspended) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_COMPATIBILITY_VERSION_NOT_SUPPORTED, String.format("$VmName %1$s", getVm().getName()), String.format("$VmVersion %1$s", getVm().getCustomCompatibilityVersion().toString()), diff --git a/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RunVmCommandTest.java b/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RunVmCommandTest.java index 5d17e6ffb89..ea28c0a7eb8 100644 --- a/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RunVmCommandTest.java +++ b/backend/manager/modules/bll/src/test/java/org/ovirt/engine/core/bll/RunVmCommandTest.java @@ -44,6 +44,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.ovirt.engine.core.bll.context.CommandContext; +import org.ovirt.engine.core.bll.interfaces.BackendInternal; import org.ovirt.engine.core.bll.network.host.NetworkDeviceHelper; import org.ovirt.engine.core.bll.network.host.VfScheduler; import org.ovirt.engine.core.bll.scheduling.SchedulingManager; @@ -70,6 +71,8 @@ import org.ovirt.engine.core.common.businessentities.storage.StorageType; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineException; +import org.ovirt.engine.core.common.errors.EngineMessage; +import org.ovirt.engine.core.common.interfaces.ErrorTranslator; import org.ovirt.engine.core.common.interfaces.VDSBrokerFrontend; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.utils.VmDeviceType; @@ -78,6 +81,7 @@ import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; +import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.dao.StorageServerConnectionDao; @@ -159,6 +163,12 @@ public class RunVmCommandTest extends BaseCommandTest { @Mock private ResourceManager resourceManager; + @Mock + private BackendInternal backend; + + @Mock + private AuditLogDirector auditLogDirector; + private static final String ACTIVE_ISO_PREFIX = "/rhev/data-center/mnt/some_computer/f6bccab4-e2f5-4e02-bba0-5748a7bc07b6/images/11111111-1111-1111-1111-111111111111"; private static final String INACTIVE_ISO_PREFIX = ""; @@ -376,6 +386,7 @@ public void setUp() { doNothing().when(command).initParametersForExternalNetworks(null, false); doReturn(Collections.emptyMap()).when(command).flushPassthroughVnicToVfMap(); doReturn(vmManager).when(command).getVmManager(); + when(backend.getErrorsTranslator()).thenReturn(mock(ErrorTranslator.class)); mockBackend(); } @@ -402,6 +413,73 @@ public void testValidate() { ValidateTestUtils.runAndAssertValidateSuccess(command); } + @Test + @MockedConfig("mockConfiguration") + public void testValidateFailsOnOlderCustomCompatibilityVersionWhenVmDown() { + final VM vm = new VM(); + vm.setStatus(VMStatus.Down); + vm.setCustomCompatibilityVersion(new Version(4, 7)); + command.setVm(vm); + StoragePool storagePool = new StoragePool(); + storagePool.setCompatibilityVersion(new Version(4, 8)); + command.setStoragePool(storagePool); + doReturn(true).when(command).checkRngDeviceClusterCompatibility(); + doReturn(true).when(command).checkPayload(any()); + doReturn(ValidationResult.VALID).when(command).checkDisksInBackupStorage(); + doReturn(false).when(command).isVmDuringBackup(); + doNothing().when(command).checkVmLeaseStorageDomain(); + Cluster cluster = new Cluster(); + cluster.setArchitecture(ArchitectureType.x86_64); + cluster.setCompatibilityVersion(new Version(4, 8)); + command.setCluster(cluster); + ValidateTestUtils.runAndAssertValidateFailure(command, + EngineMessage.ACTION_TYPE_FAILED_VM_COMPATIBILITY_VERSION_NOT_SUPPORTED); + } + + @Test + @MockedConfig("mockConfiguration") + public void testValidateSucceedsOnOlderCustomCompatibilityVersionWhenVmSuspended() { + final VM vm = new VM(); + vm.setStatus(VMStatus.Suspended); + vm.setCustomCompatibilityVersion(new Version(4, 7)); + command.setVm(vm); + StoragePool storagePool = new StoragePool(); + storagePool.setCompatibilityVersion(new Version(4, 8)); + command.setStoragePool(storagePool); + doReturn(true).when(command).checkRngDeviceClusterCompatibility(); + doReturn(true).when(command).checkPayload(any()); + doReturn(ValidationResult.VALID).when(command).checkDisksInBackupStorage(); + doReturn(false).when(command).isVmDuringBackup(); + doNothing().when(command).checkVmLeaseStorageDomain(); + Cluster cluster = new Cluster(); + cluster.setArchitecture(ArchitectureType.x86_64); + cluster.setCompatibilityVersion(new Version(4, 8)); + command.setCluster(cluster); + ValidateTestUtils.runAndAssertValidateSuccess(command); + } + + @Test + @MockedConfig("mockConfiguration") + public void testValidateSucceedsOnOlderCustomCompatibilityVersionWhenVmPaused() { + final VM vm = new VM(); + vm.setStatus(VMStatus.Paused); + vm.setCustomCompatibilityVersion(new Version(4, 7)); + command.setVm(vm); + StoragePool storagePool = new StoragePool(); + storagePool.setCompatibilityVersion(new Version(4, 8)); + command.setStoragePool(storagePool); + doReturn(true).when(command).checkRngDeviceClusterCompatibility(); + doReturn(true).when(command).checkPayload(any()); + doReturn(ValidationResult.VALID).when(command).checkDisksInBackupStorage(); + doReturn(false).when(command).isVmDuringBackup(); + doNothing().when(command).checkVmLeaseStorageDomain(); + Cluster cluster = new Cluster(); + cluster.setArchitecture(ArchitectureType.x86_64); + cluster.setCompatibilityVersion(new Version(4, 8)); + command.setCluster(cluster); + ValidateTestUtils.runAndAssertValidateSuccess(command); + } + public static Stream validateUnsupportedRng() { Set sourcesEmpty = new HashSet<>(); Set sourcesRandom = new HashSet<>();