From c1af36f8fc9bcba6480f7f0daf2542c991f75e18 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Thu, 26 Mar 2026 09:47:49 -0300 Subject: [PATCH] [4.22] Prevent unmanaging or reinstalling a VM if it is part of a CKS cluster (#12800) --- .../main/java/com/cloud/vm/UserVmManager.java | 5 ++ .../java/com/cloud/vm/UserVmManagerImpl.java | 9 ++++ .../vm/UnmanagedVMsManagerImpl.java | 7 +++ .../com/cloud/vm/UserVmManagerImplTest.java | 4 ++ .../vm/UnmanagedVMsManagerImplTest.java | 53 +++++++++++++++++++ 5 files changed, 78 insertions(+) diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index c035165a3fa..0a744709644 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -199,4 +199,9 @@ public interface UserVmManager extends UserVmService { Boolean getDestroyRootVolumeOnVmDestruction(Long domainId); + /** + * @return true if the VM is part of a CKS cluster, false otherwise. + */ + boolean isVMPartOfAnyCKSCluster(VMInstanceVO vm); + } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 1404ab206fb..4cb721666bd 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -8785,6 +8785,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException(String.format("Operation not supported for instance: %s", vm.getName())); } + if (isVMPartOfAnyCKSCluster(vm)) { + throw new UnsupportedServiceException("Cannot restore VM with id = " + vm.getUuid() + + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before restoring."); + } _accountMgr.checkAccess(caller, null, true, vm); VMTemplateVO template; @@ -9986,6 +9990,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return DestroyRootVolumeOnVmDestruction.valueIn(domainId); } + @Override + public boolean isVMPartOfAnyCKSCluster(VMInstanceVO vm) { + return kubernetesServiceHelpers.get(0).findByVmId(vm.getId()) != null; + } + private void setVncPasswordForKvmIfAvailable(Map customParameters, UserVmVO vm) { if (customParameters.containsKey(VmDetailConstants.KVM_VNC_PASSWORD) && StringUtils.isNotEmpty(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD))) { diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index fa97236aca4..e043791c6bf 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -2304,6 +2304,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { * Perform validations before attempting to unmanage a VM from CloudStack: * - VM must not have any associated volume snapshot * - VM must not have an attached ISO + * - VM must not belong to any CKS cluster + * @throws UnsupportedServiceException in case any of the validations above fail */ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { if (hasVolumeSnapshotsPriorToUnmanageVM(vmVO)) { @@ -2315,6 +2317,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + " as there is an ISO attached. Please detach ISO before unmanaging."); } + + if (userVmManager.isVMPartOfAnyCKSCluster(vmVO)) { + throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before unmanaging."); + } } private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) { diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 21bcec59a5d..783be00c449 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -59,6 +59,7 @@ import java.util.Map; import java.util.TimeZone; import java.util.UUID; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.storage.dao.SnapshotPolicyDao; import com.cloud.utils.fsm.NoTransitionException; import org.apache.cloudstack.acl.ControlledEntity; @@ -1476,6 +1477,9 @@ public class UserVmManagerImplTest { when(cmd.getVmId()).thenReturn(vmId); when(cmd.getTemplateId()).thenReturn(2L); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); + KubernetesServiceHelper helper = mock(KubernetesServiceHelper.class); + when(helper.findByVmId(anyLong())).thenReturn(null); + userVmManagerImpl.setKubernetesServiceHelpers(Collections.singletonList(helper)); userVmManagerImpl.restoreVM(cmd); } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 541148b5ddf..98e6388f3d6 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -39,6 +40,9 @@ import java.util.Map; import java.util.UUID; import com.cloud.offering.DiskOffering; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.dao.SnapshotDao; import com.cloud.vm.ImportVMTaskVO; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseGenerator; @@ -241,6 +245,8 @@ public class UnmanagedVMsManagerImplTest { private StoragePoolHostDao storagePoolHostDao; @Mock private ImportVmTasksManager importVmTasksManager; + @Mock + private SnapshotDao snapshotDao; @Mock private VMInstanceVO virtualMachine; @@ -568,6 +574,53 @@ public class UnmanagedVMsManagerImplTest { unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); } + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithVolumeSnapshotsFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + VolumeVO volumeVO = mock(VolumeVO.class); + long volumeId = 20L; + when(volumeVO.getId()).thenReturn(volumeId); + SnapshotVO snapshotVO = mock(SnapshotVO.class); + when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); + when(snapshotDao.listByVolumeId(volumeId)).thenReturn(Collections.singletonList(snapshotVO)); + when(volumeDao.findByInstance(virtualMachineId)).thenReturn(Collections.singletonList(volumeVO)); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithAssociatedIsoFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(userVmVO.getIsoId()).thenReturn(1L); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceBelongingToCksClusterFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(userVmManager.isVMPartOfAnyCKSCluster(virtualMachine)).thenReturn(true); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + @Test public void testListRemoteInstancesTest() { ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class);